Java tutorial
/* * OnionCoffee - Anonymous Communication through TOR Network * Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* * silvertunnel.org Netlib - Java library to easily access anonymity networks * Copyright (c) 2009-2012 silvertunnel.org * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.silvertunnel.netlib.layer.tor.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.Cipher; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1OutputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.RSAPublicKeyStructure; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.encodings.OAEPEncoding; import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.RSAEngine; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.jce.provider.JCERSAPrivateCrtKey; import org.bouncycastle.jce.provider.JCERSAPrivateKey; import org.bouncycastle.jce.provider.JCERSAPublicKey; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.util.encoders.Base64; /** * this class contains utility functions concerning encryption * * @author Lexi Pimenidis * @author Andriy Panchenko * @author Michael Koellejan * @author hapke */ public class Encryption { private static final Logger log = Logger.getLogger(Encryption.class.getName()); public static final String DIGEST_ALGORITHM = "SHA-1"; private static final String PK_ALGORITHM = "RSA"; static { try { // install BC, if not already done if (Security.getProvider("BC") == null) { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); // Security.insertProviderAt(new // org.bouncycastle.jce.provider.BouncyCastleProvider(),2); } } catch (Throwable t) { log.log(Level.SEVERE, "Cannot initialize class Encryption", t); } } /** * returns the SHA-1 of the input * * @param input * @return digest value */ public static byte[] getDigest(byte[] input) { return getDigest(DIGEST_ALGORITHM, input); } /** * returns the digest of the input * * @param algorithm e.g. "SHA-1" * @param input * @return digest value */ public static byte[] getDigest(String algorithm, byte[] input) { try { MessageDigest md = MessageDigest.getInstance(algorithm); md.reset(); md.update(input, 0, input.length); return md.digest(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * @return implementation of the SHA-1 message digest; reset() already called */ public static MessageDigest getMessagesDigest() { try { MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM); md.reset(); return md; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * Calculate the digest but do not touch md. * * @param md * @return the digest, calculated with a clone of md */ public static byte[] intermediateDigest(MessageDigest md) { try { // ugly fix around the behavior on digests MessageDigest mdClone = (MessageDigest) md.clone(); return mdClone.digest(); } catch (Exception e) { throw new RuntimeException(e); } } /** * checks signature of PKCS1-padded SHA1 hash of the input * * Hint: A different implementation of this method can be found in the svn history revision<=229. * * @param signature * signature to check * @param signingKey * public key from signing * @param input * byte array, signature is made over * * @return true, if the signature is correct * */ public static boolean verifySignature(byte[] signature, RSAPublicKeyStructure signingKey, byte[] input) { byte[] hash = getDigest(input); try { RSAKeyParameters myRSAKeyParameters = new RSAKeyParameters(false, signingKey.getModulus(), signingKey.getPublicExponent()); PKCS1Encoding pkcsAlg = new PKCS1Encoding(new RSAEngine()); pkcsAlg.init(false, myRSAKeyParameters); byte[] decryptedSignature = pkcsAlg.processBlock(signature, 0, signature.length); return Encoding.arraysEqual(hash, decryptedSignature); } catch (Exception e) { log.log(Level.WARNING, "unexpected", e); return false; } } /** * checks row signature * * @param signature * signature to check * @param signingKey * public key from signing * @param input * byte array, signature is made over * * @return true, if the signature is correct * */ public static boolean verifySignatureXXXX(byte[] signature, RSAPublicKeyStructure signingKey, byte[] input) { byte[] hash = getDigest(input); try { Signature sig = Signature.getInstance("SHA1withRSA"); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(signingKey.getModulus(), signingKey.getPublicExponent()); PublicKey pubKey = keyFactory.generatePublic(keySpec); sig.initVerify(pubKey); sig.update(input); log.info(""); log.info(" HERE -> " + sig.verify(signature)); RSAKeyParameters myRSAKeyParameters = new RSAKeyParameters(false, signingKey.getModulus(), signingKey.getPublicExponent()); RSAEngine rsaAlg = new RSAEngine(); rsaAlg.init(false, myRSAKeyParameters); byte[] decryptedSignature = rsaAlg.processBlock(signature, 0, signature.length); log.info(" inpu = " + Encoding.toHexString(input)); log.info(" hash = " + Encoding.toHexString(hash)); log.info(""); log.info(" sign = " + Encoding.toHexString(signature)); log.info(" decr = " + Encoding.toHexString(decryptedSignature)); return Encoding.arraysEqual(hash, decryptedSignature); } catch (Exception e) { log.log(Level.WARNING, "unexpected", e); return false; } } public static boolean verifySignature(byte[] signature, PublicKey signingKey, byte[] data) { try { Cipher cipher = Cipher.getInstance(PK_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, signingKey); byte[] decryptedDigest = cipher.doFinal(signature); byte[] dataDigest = getDigest(DIGEST_ALGORITHM, data); if (decryptedDigest != null && dataDigest != null && decryptedDigest.length > dataDigest.length) { // try to fix bug in security calculation with OpenJDK-6 java web start (ticket #59) log.warning( "verifySignature(): try to fix bug in security calculation with OpenJDK-6 java web start (ticket #59)"); log.warning("verifySignature(): original decryptedDigest=" + Encoding.toHexString(decryptedDigest)); log.warning("verifySignature(): dataDigest =" + Encoding.toHexString(dataDigest)); byte[] fixedDecryptedDigest = new byte[dataDigest.length]; System.arraycopy(decryptedDigest, decryptedDigest.length - dataDigest.length, fixedDecryptedDigest, 0, dataDigest.length); decryptedDigest = fixedDecryptedDigest; } boolean verificationSuccessful = Arrays.equals(decryptedDigest, dataDigest); if (verificationSuccessful == false) { log.info("verifySignature(): decryptedDigest=" + Encoding.toHexString(decryptedDigest)); log.info("verifySignature(): dataDigest =" + Encoding.toHexString(dataDigest)); } return verificationSuccessful; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * sign some data using a private key and PKCS#1 v1.5 padding * * @param data * the data to be signed * @param signingKey * the key to sign the data * @return a signature */ public static byte[] signData(byte[] data, RSAKeyParameters signingKey) { try { byte[] hash = Encryption.getDigest(data); PKCS1Encoding pkcs1 = new PKCS1Encoding(new RSAEngine()); pkcs1.init(true, signingKey); return pkcs1.processBlock(hash, 0, hash.length); } catch (InvalidCipherTextException e) { log.log(Level.WARNING, "Common.signData(): " + e.getMessage(), e); return null; } } /** * sign some data using a private kjey and PKCS#1 v1.5 padding * * @param data * the data to be signed * @param signingKey * the key to sign the data * @return a signature */ public static byte[] signData(byte[] data, PrivateKey signingKey) { try { Cipher cipher = Cipher.getInstance(PK_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, signingKey); return cipher.doFinal(getDigest(DIGEST_ALGORITHM, data)); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** used to encode a signature in PEM */ public static String binarySignatureToPEM(byte[] signature) { String sigB64 = Encoding.toBase64(signature); StringBuffer sig = new StringBuffer(); sig.append("-----BEGIN SIGNATURE-----\n"); while (sigB64.length() > 64) { sig.append(sigB64.substring(0, 64) + "\n"); sigB64 = sigB64.substring(64); } sig.append(sigB64 + "\n"); sig.append("-----END SIGNATURE-----\n"); return sig.toString(); } /** * makes RSA public key from PEM string * * @param s PEM string that contains the key * @return * @see JCERSAPublicKey */ public static RSAPublicKey extractPublicRSAKey(String s) { RSAPublicKey theKey; try { PEMReader reader = new PEMReader(new StringReader(s)); Object o = reader.readObject(); if (!(o instanceof JCERSAPublicKey)) { throw new IOException("Encryption.extractPublicRSAKey: no public key found in string '" + s + "'"); } JCERSAPublicKey JCEKey = (JCERSAPublicKey) o; theKey = getRSAPublicKey(JCEKey.getModulus(), JCEKey.getPublicExponent()); } catch (Exception e) { log.warning("Encryption.extractPublicRSAKey: Caught exception:" + e.getMessage()); theKey = null; } return theKey; } /** * makes RSA private key from PEM string * * @param s PEM string that contains the key * @return * @see JCERSAPublicKey */ public static RSAKeyPair extractRSAKeyPair(String s) { RSAKeyPair rsaKeyPair; try { // parse PEMReader reader = new PEMReader(new StringReader(s)); Object o = reader.readObject(); // check types if (!(o instanceof KeyPair)) { throw new IOException("Encryption.extractRSAKeyPair: no private key found in string '" + s + "'"); } KeyPair keyPair = (KeyPair) o; if (!(keyPair.getPrivate() instanceof JCERSAPrivateKey)) { throw new IOException( "Encryption.extractRSAKeyPair: no private key found in key pair of string '" + s + "'"); } if (!(keyPair.getPublic() instanceof JCERSAPublicKey)) { throw new IOException( "Encryption.extractRSAKeyPair: no public key found in key pair of string '" + s + "'"); } // convert keys and pack them together into a key pair RSAPrivateCrtKey privateKey = (JCERSAPrivateCrtKey) keyPair.getPrivate(); log.finer("JCEPrivateKey=" + privateKey); RSAPublicKey publicKey = (JCERSAPublicKey) keyPair.getPublic(); rsaKeyPair = new RSAKeyPair(publicKey, privateKey); } catch (Exception e) { log.warning("Encryption.extractPrivateRSAKey: Caught exception:" + e.getMessage()); rsaKeyPair = null; } return rsaKeyPair; } /** * Converts RSA private key to PEM string. * * @param rsaKeyPair * * @return PEM string */ public static String getPEMStringFromRSAKeyPair(RSAKeyPair rsaKeyPair) { StringWriter pemStrWriter = new StringWriter(); PEMWriter pemWriter = new PEMWriter(pemStrWriter); try { KeyPair keyPair = new KeyPair(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate()); //pemWriter.writeObject(keyPair); pemWriter.writeObject(keyPair.getPrivate()); //pemWriter.flush(); pemWriter.close(); } catch (IOException e) { log.warning("Caught exception:" + e.getMessage()); return ""; } return pemStrWriter.toString(); } /** * Create a key based on the parameters. * * @param modulus * @param publicExponent * @return the key */ public static RSAPublicKey getRSAPublicKey(BigInteger modulus, BigInteger publicExponent) { try { return (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new RSAPublicKeySpec(modulus, publicExponent)); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * Create a key based on the parameters. * * @param modulus * @param publicExponent * @return the key */ public static RSAPrivateKey getRSAPrivateKey(BigInteger modulus, BigInteger privateExponent) { try { return (RSAPrivateKey) KeyFactory.getInstance("RSA") .generatePrivate(new RSAPrivateKeySpec(modulus, privateExponent)); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * makes RSA public key from bin byte array * * @param s * string that contais the key * @return * @see JCERSAPublicKey */ public static RSAPublicKey extractBinaryRSAKey(byte[] b) { RSAPublicKey theKey; try { ASN1InputStream ais = new ASN1InputStream(b); Object asnObject = ais.readObject(); ASN1Sequence sequence = (ASN1Sequence) asnObject; RSAPublicKeyStructure tempKey = new RSAPublicKeyStructure(sequence); theKey = getRSAPublicKey(tempKey.getModulus(), tempKey.getPublicExponent()); } catch (IOException e) { log.warning("Caught exception:" + e.getMessage()); theKey = null; } return theKey; } /** * copy from one format to another */ public static RSAPublicKey getRSAPublicKey(JCERSAPublicKey jpub) { return getRSAPublicKey(jpub.getModulus(), jpub.getPublicExponent()); } /** * converts a RSAPublicKey into PKCS1-encoding (ASN.1) * * @param rsaPublicKey * @see JCERSAPublicKey * @return PKCS1-encoded RSA PUBLIC KEY */ public static byte[] getPKCS1EncodingFromRSAPublicKey(RSAPublicKey pubKeyStruct) { try { RSAPublicKeyStructure myKey = new RSAPublicKeyStructure(pubKeyStruct.getModulus(), pubKeyStruct.getPublicExponent()); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ASN1OutputStream aOut = new ASN1OutputStream(bOut); aOut.writeObject(myKey.toASN1Object()); return bOut.toByteArray(); } catch (Exception e) { return null; } } /** * converts a JCERSAPublicKey into PEM/PKCS1-encoding * * @param rsaPublicKey * @see RSAPublicKeyStructure * @return PEM-encoded RSA PUBLIC KEY */ public static String getPEMStringFromRSAPublicKey(RSAPublicKey rsaPublicKey) { // mrk: this was awful to program. Remeber: There are two entirely // different // standard formats for rsa public keys. Bouncy castle does only support // the // one we can't use for TOR directories. StringBuffer tmpDirSigningKey = new StringBuffer(); try { tmpDirSigningKey.append("-----BEGIN RSA PUBLIC KEY-----\n"); byte[] base64Encoding = Base64.encode(getPKCS1EncodingFromRSAPublicKey(rsaPublicKey)); for (int i = 0; i < base64Encoding.length; i++) { tmpDirSigningKey.append((char) base64Encoding[i]); if (((i + 1) % 64) == 0) tmpDirSigningKey.append("\n"); } tmpDirSigningKey.append("\n"); tmpDirSigningKey.append("-----END RSA PUBLIC KEY-----\n"); } catch (Exception e) { return null; } return tmpDirSigningKey.toString(); } /** * encrypt data with asymmetric key. create asymmetricla encrypted data:<br> * <ul> * <li>OAEP padding [42 bytes] (RSA-encrypted) * <li>Symmetric key [16 bytes] FIXME: we assume that we ALWAYS need this * <li>First part of data [70 bytes] * <li>Second part of data [x-70 bytes] (Symmetrically encrypted) * <ul> * encrypt and store in result * * @param pub * @param symmetricKey AES key * @param data * to be encrypted, needs currently to be at least 70 bytes long * @return the first half of the key exchange, ready to be send to the other * partner */ public static byte[] asymEncrypt(RSAPublicKey pub, byte[] symmetricKey, byte[] data) throws TorException { if (data == null) { throw new NullPointerException("can't encrypt NULL data"); } if (data.length < 70) { throw new TorException("input array too short"); } try { int encryptedBytes = 0; // initialize OAEP OAEPEncoding oaep = new OAEPEncoding(new RSAEngine()); oaep.init(true, new RSAKeyParameters(false, pub.getModulus(), pub.getPublicExponent())); // apply RSA+OAEP encryptedBytes = oaep.getInputBlockSize(); byte[] oaepInput = new byte[encryptedBytes]; System.arraycopy(data, 0, oaepInput, 0, encryptedBytes); byte[] part1 = oaep.encodeBlock(oaepInput, 0, encryptedBytes); // initialize AES AESCounterMode aes = new AESCounterMode(true, symmetricKey); // apply AES byte[] aesInput = new byte[data.length - encryptedBytes]; System.arraycopy(data, encryptedBytes, aesInput, 0, aesInput.length); byte part2[] = aes.processStream(aesInput); // replace unencrypted data byte[] result = new byte[part1.length + part2.length]; System.arraycopy(part1, 0, result, 0, part1.length); System.arraycopy(part2, 0, result, part1.length, part2.length); return result; } catch (InvalidCipherTextException e) { log.severe("Node.asymEncrypt(): can't encrypt cipher text:" + e.getMessage()); throw new TorException("InvalidCipherTextException:" + e.getMessage()); } } /** * decrypt data with asymmetric key. create asymmetrically encrypted data:<br> * <ul> * <li>OAEP padding [42 bytes] (RSA-encrypted) * <li>Symmetric key [16 bytes] * <li>First part of data [70 bytes] * <li>Second part of data [x-70 bytes] (Symmetrically encrypted) * <ul> * encrypt and store in result * * @param priv * key to use for decryption * @param data * to be decrypted, needs currently to be at least 70 bytes long * @return raw data */ public static byte[] asymDecrypt(RSAPrivateKey priv, byte[] data) throws TorException { if (data == null) { throw new NullPointerException("can't encrypt NULL data"); } if (data.length < 70) { throw new TorException("input array too short"); } try { int encryptedBytes = 0; // init OAEP OAEPEncoding oaep = new OAEPEncoding(new RSAEngine()); oaep.init(false, new RSAKeyParameters(true, priv.getModulus(), priv.getPrivateExponent())); // apply RSA+OAEP encryptedBytes = oaep.getInputBlockSize(); byte[] oaepInput = new byte[encryptedBytes]; System.arraycopy(data, 0, oaepInput, 0, encryptedBytes); byte[] part1 = oaep.decodeBlock(oaepInput, 0, encryptedBytes); // extract symmetric key byte[] symmetricKey = new byte[16]; System.arraycopy(part1, 0, symmetricKey, 0, 16); // init AES AESCounterMode aes = new AESCounterMode(true, symmetricKey); // apply AES byte[] aesInput = new byte[data.length - encryptedBytes]; System.arraycopy(data, encryptedBytes, aesInput, 0, aesInput.length); byte part2[] = aes.processStream(aesInput); // replace unencrypted data byte[] result = new byte[part1.length - 16 + part2.length]; System.arraycopy(part1, 16, result, 0, part1.length - 16); System.arraycopy(part2, 0, result, part1.length - 16, part2.length); return result; } catch (InvalidCipherTextException e) { log.severe("CommonEncryption.asymDecrypt(): can't decrypt cipher text:" + e.getMessage()); throw new TorException("CommonEncryption.asymDecrypt(): InvalidCipherTextException:" + e.getMessage()); } } /** * Create a fresh RSA key pair. * * @return a new RSAKeyPair */ public static RSAKeyPair createNewRSAKeyPair() { final int KEY_STRENGTH = 1024; final int KEY_CERTAINTY = 80; // use 112 for strength=2048 try { // Generate a 1024-bit RSA key pair KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(KEY_STRENGTH); KeyPair keypair = keyGen.genKeyPair(); RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keypair.getPrivate(); RSAPublicKey publicKey = (RSAPublicKey) keypair.getPublic(); log.info("privateKey=" + privateKey); log.info("publicKey=" + publicKey); RSAKeyPair result = new RSAKeyPair(publicKey, privateKey); return result; } catch (NoSuchAlgorithmException e) { log.log(Level.SEVERE, "Could not create new key pair", e); throw new RuntimeException(e); } } }