Java tutorial
/* * ------------------------------------------------------------------------------ * "THE BEER-WARE LICENSE" (Ramo's Revision): * <ramo@goodvikings.com> wrote this file. As long as you retain this notice on * this and any derivative works you can do whatever you want with this stuff. * If we meet some day, and you think this stuff is worth it, you can buy me a * beer in return - Ramo * ------------------------------------------------------------------------------ */ package com.goodvikings.cryptim.api; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Paths; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.text.ParseException; import java.util.Date; import java.util.TreeMap; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1UTCTime; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERUTCTime; import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPLiteralDataGenerator; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPOnePassSignatureList; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.util.io.Streams; import sun.security.rsa.RSAPublicKeyImpl; class KeyRing { public KeyRing() { keys = new TreeMap<>(); nicks = new TreeMap<>(); salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; rand = new SecureRandom(); } public void open(String filename, CallbackHandler cbh, boolean createIfNotExists) throws BadPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException, ParseException, CryptimException { this.filename = filename; File f = new File(filename); if (f.exists()) { openExisting(cbh); } else { if (createIfNotExists) { genOwnKeys(); } else { throw new CryptimException("Key db doesn't exist, not creating one"); } } } public void saveKDB(CallbackHandler cbh) throws BadPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException { saveKDB(filename, cbh); } public void saveKDB(String filename, CallbackHandler cbh) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, PGPException { PasswordCallback pcb = new PasswordCallback("Password", false); try { cbh.handle(new Callback[] { pcb }); } catch (UnsupportedCallbackException e) { // I just set the callback, this exception will never be thrown } byte data[] = ASN1EncodeKeys(); SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALG, PROVIDER); SecretKey key = skf.generateSecret(new PBEKeySpec(pcb.getPassword(), salt, ITERATION_COUNT, SYMM_KEY_SIZE)); pcb.clearPassword(); Cipher c = Cipher.getInstance(KEY_ALG, PROVIDER); c.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, ITERATION_COUNT)); byte[] ciphertext = c.doFinal(data); FileOutputStream fos = new FileOutputStream(filename); fos.write(ciphertext); fos.close(); } public void addPublicKey(String jid, String nick, PGPPublicKey key) { keys.put(jid, key); nicks.put(jid, nick); } public PGPPublicKey getMyPublic() { return kp.getPublicKey(); } public void signEncryptMessage(InputStream in, OutputStream out, String jid) throws IOException, PGPException, SignatureException { out = new ArmoredOutputStream(out); PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(SYMM_ALG) .setWithIntegrityPacket(true).setSecureRandom(rand).setProvider(PROVIDER)); encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(keys.get(jid)).setProvider(PROVIDER)); OutputStream encryptedOut = encGen.open(out, new byte[BUFFER_SIZE]); OutputStream compressedData = new PGPCompressedDataGenerator(COMP_ALG).open(encryptedOut); PGPSignatureGenerator sGen = new PGPSignatureGenerator( new JcaPGPContentSignerBuilder(kp.getPrivateKey().getPublicKeyPacket().getAlgorithm(), HASH_ALG) .setProvider(PROVIDER)); sGen.init(PGPSignature.BINARY_DOCUMENT, kp.getPrivateKey()); sGen.generateOnePassVersion(false).encode(compressedData); OutputStream finalOut = new PGPLiteralDataGenerator().open(compressedData, PGPLiteralData.BINARY, "", new Date(), new byte[BUFFER_SIZE]); byte[] buf = new byte[BUFFER_SIZE]; int len; while ((len = in.read(buf)) > 0) { finalOut.write(buf, 0, len); sGen.update(buf, 0, len); } in.close(); finalOut.close(); sGen.generate().encode(compressedData); compressedData.close(); encryptedOut.close(); out.close(); } public boolean decryptVerifyMessage(InputStream in, OutputStream out, String jid) throws IOException, PGPException, SignatureException { in = new ArmoredInputStream(in); PGPObjectFactory plainFact = new PGPObjectFactory( ((PGPPublicKeyEncryptedData) ((PGPEncryptedDataList) new PGPObjectFactory(in).nextObject()) .getEncryptedDataObjects().next()) .getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(PROVIDER) .build(kp.getPrivateKey()))); PGPOnePassSignatureList onePassSignatureList = null; PGPSignatureList signatureList = null; PGPCompressedData compressedData = null; Object obj = plainFact.nextObject(); ByteArrayOutputStream actualOutput = new ByteArrayOutputStream(); while (obj != null) { if (obj instanceof PGPCompressedData) { compressedData = (PGPCompressedData) obj; plainFact = new PGPObjectFactory(compressedData.getDataStream()); obj = plainFact.nextObject(); } if (obj instanceof PGPLiteralData) { Streams.pipeAll(((PGPLiteralData) obj).getInputStream(), actualOutput); } else if (obj instanceof PGPOnePassSignatureList) { onePassSignatureList = (PGPOnePassSignatureList) obj; } else if (obj instanceof PGPSignatureList) { signatureList = (PGPSignatureList) obj; } else { throw new PGPException("message unknown message type."); } obj = plainFact.nextObject(); } actualOutput.close(); byte[] output = actualOutput.toByteArray(); PGPOnePassSignature ops = onePassSignatureList.get(0); ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider(PROVIDER), keys.get(jid)); ops.update(output); out.write(output); out.flush(); out.close(); return ops.verify(signatureList.get(0)); } public boolean hasKeyForUser(String user) { if (keys.containsKey(user)) { return true; } for (String str : nicks.keySet()) { if (nicks.get(str).equals(user)) { return keys.containsKey(str); } } return false; } private boolean openExisting(CallbackHandler cbh) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, PGPException, ParseException { PasswordCallback pcb = new PasswordCallback("Password", false); try { cbh.handle(new Callback[] { pcb }); } catch (UnsupportedCallbackException e) { // I just set the callback, this exception will never be thrown } byte[] ciphertext = Files.readAllBytes(Paths.get(filename)); SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALG, PROVIDER); SecretKey key = skf.generateSecret(new PBEKeySpec(pcb.getPassword(), salt, ITERATION_COUNT, SYMM_KEY_SIZE)); pcb.clearPassword(); Cipher c = Cipher.getInstance(KEY_ALG, PROVIDER); c.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, ITERATION_COUNT)); byte[] plain = c.doFinal(ciphertext); ASN1DecodeKeys(plain); return testKeyRing(); } private void genOwnKeys() throws PGPException { RSAKeyPairGenerator kpg = new RSAKeyPairGenerator(); kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), ASYMM_KEY_SIZE, 90)); kp = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpg.generateKeyPair(), new Date()); } private byte[] ASN1EncodeKeys() throws IOException, PGPException { JcaPGPKeyConverter converter = new JcaPGPKeyConverter(); PrivateKey priv = converter.getPrivateKey(kp.getPrivateKey()); PublicKey pub = converter.getPublicKey(kp.getPublicKey()); ASN1EncodableVector pubSeq = new ASN1EncodableVector(); for (String jid : keys.keySet()) { pubSeq.add(new DERSequence(new ASN1Encodable[] { new DERUTF8String(jid), new DERUTF8String(nicks.get(jid)), new DERUTCTime(keys.get(jid).getCreationTime()), new DEROctetString(converter.getPublicKey(keys.get(jid)).getEncoded()) })); } DERSequence seq = new DERSequence(new ASN1Encodable[] { new DERSequence(new ASN1Encodable[] { new DERUTCTime(kp.getPublicKey().getCreationTime()), new DEROctetString(pub.getEncoded()) }), new DEROctetString(priv.getEncoded()), new DERSequence(pubSeq) }); return seq.getEncoded(); } private void ASN1DecodeKeys(byte[] plain) throws IOException, PGPException, NoSuchProviderException, ParseException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException { JcaPGPKeyConverter converter = new JcaPGPKeyConverter(); ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(plain)).readObject(); PGPPublicKey pub = converter.getPGPPublicKey(PGPPublicKey.RSA_GENERAL, new RSAPublicKeyImpl( ((ASN1OctetString) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(1)).getOctets()), ((ASN1UTCTime) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(0)).getAdjustedDate()); kp = new PGPKeyPair(pub, converter.getPGPPrivateKey(pub, KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(((ASN1OctetString) seq.getObjectAt(1)).getOctets())))); ASN1Sequence keySeq = (ASN1Sequence) seq.getObjectAt(2); for (int i = 0; i < keySeq.size(); i++) { keys.put(((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(0)).getString(), converter.getPGPPublicKey(PGPPublicKey.RSA_GENERAL, new RSAPublicKeyImpl( ((ASN1OctetString) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(3)).getOctets()), ((ASN1UTCTime) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(2)) .getAdjustedDate())); nicks.put(((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(0)).getString(), ((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(1)).getString()); } } private boolean testKeyRing() throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException { JcaPGPKeyConverter conv = new JcaPGPKeyConverter(); PublicKey pub = conv.getPublicKey(kp.getPublicKey()); PrivateKey priv = conv.getPrivateKey(kp.getPrivateKey()); String testData = "The quick brown fox jumps over the lazy dog."; Cipher enc = Cipher.getInstance(pub.getAlgorithm(), PROVIDER); enc.init(Cipher.ENCRYPT_MODE, pub); byte[] ciphertext = enc.doFinal(testData.getBytes()); Cipher dec = Cipher.getInstance(pub.getAlgorithm(), PROVIDER); dec.init(Cipher.DECRYPT_MODE, priv); return new String(dec.doFinal(ciphertext)).equals(testData); } private TreeMap<String, PGPPublicKey> keys; private TreeMap<String, String> nicks; private PGPKeyPair kp; private String filename; private byte[] salt; private SecureRandom rand; // Constants private static final String KEY_ALG = "PBEWITHSHA256AND256BITAES-CBC-BC"; private static final int SYMM_KEY_SIZE = 256; private static final int ASYMM_KEY_SIZE = 4096; private static final int ITERATION_COUNT = 10000; private static final String PROVIDER = "BC"; private static final int BUFFER_SIZE = 4096; private static final int SYMM_ALG = PGPEncryptedData.AES_256; private static final int COMP_ALG = PGPCompressedData.ZIP; private static final int HASH_ALG = PGPUtil.SHA512; }