Java tutorial
/* * Copyright 2012-2013 Mathias Herberts * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.geoxp.oss; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.SignatureException; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.crypto.AsymmetricBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.AESWrapEngine; import org.bouncycastle.crypto.engines.RSABlindedEngine; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPLiteralDataGenerator; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.util.encoders.Hex; import com.etsy.net.JUDS; import com.etsy.net.UnixDomainSocketClient; /** * Helper class containing various methods used to * ease up cryptographic operations */ public class CryptoHelper { /** * Default algorithm to use when generating signatures */ public static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256WithRSA"; private static final String SSH_DSS_PREFIX = "ssh-dss"; private static final String SSH_RSA_PREFIX = "ssh-rsa"; /** * SecureRandom used by the class */ private static SecureRandom sr = null; static { // // Add BouncyCastleProvider // Security.addProvider(new BouncyCastleProvider()); // // Create PRNG, will be null if provider/algorithm not found // try { sr = SecureRandom.getInstance("SHA1PRNG", "SUN"); } catch (NoSuchProviderException nspe) { } catch (NoSuchAlgorithmException nsae) { } if (null == sr) { sr = new SecureRandom(); } } public static SecureRandom getSecureRandom() { return CryptoHelper.sr; } /** * Pad data using PKCS7. * * @param alignment Alignement on which to pad, e.g. 8 * @param data Data to pad * @return The padded data */ public static byte[] padPKCS7(int alignment, byte[] data, int offset, int len) { // // Allocate the target byte array. Its size is a multiple of 'alignment'. // If data to pad is a multiple of 'alignment', the target array will be // 'alignment' bytes longer than the data to pad. // byte[] target = new byte[len + (alignment - len % alignment)]; // // Copy the data to pad into the target array // System.arraycopy(data, offset, target, 0, len); // // Add padding bytes // PKCS7Padding padding = new PKCS7Padding(); padding.addPadding(target, len); return target; } public static byte[] padPKCS7(int alignment, byte[] data) { return padPKCS7(alignment, data, 0, data.length); } /** * Remove PKCS7 padding from padded data * @param padded The padded data to 'unpad' * @return The original unpadded data * @throws InvalidCipherTextException if data is not correctly padded */ public static byte[] unpadPKCS7(byte[] padded) throws InvalidCipherTextException { PKCS7Padding padding = new PKCS7Padding(); // // Determine length of padding // int pad = padding.padCount(padded); // // Allocate array for unpadded data // byte[] unpadded = new byte[padded.length - pad]; // // Copy data without the padding // System.arraycopy(padded, 0, unpadded, 0, padded.length - pad); return unpadded; } /** * Protect some data using AES Key Wrapping * * @param key AES wrapping key * @param data Data to wrap * @param offset Where does the data start in 'data' * @param len How long is the data * @param nonce Should a random prefix be added to the data prior to wrapping * @return The wrapped data */ public static byte[] wrapAES(byte[] key, byte[] data, int offset, int len, boolean nonce) { // // Initialize AES Wrap Engine for wrapping // AESWrapEngine aes = new AESWrapEngine(); KeyParameter keyparam = new KeyParameter(key); aes.init(true, keyparam); if (nonce) { byte[] nonced = new byte[len + OSS.NONCE_BYTES]; byte[] noncebytes = new byte[OSS.NONCE_BYTES]; getSecureRandom().nextBytes(noncebytes); System.arraycopy(noncebytes, 0, nonced, 0, OSS.NONCE_BYTES); System.arraycopy(data, offset, nonced, OSS.NONCE_BYTES, len); data = nonced; offset = 0; len = data.length; } // // Pad the data on an 8 bytes boundary // byte[] padded = padPKCS7(8, data, offset, len); // // Wrap data and return it // return aes.wrap(padded, 0, padded.length); } public static byte[] wrapAES(byte[] key, byte[] data) { return wrapAES(key, data, 0, data.length, false); } public static byte[] wrapBlob(byte[] key, byte[] blob) { return wrapAES(key, blob, 0, blob.length, true); } /** * Unwrap data protected by AES Key Wrapping * * @param key Key used to wrap the data * @param data Wrapped data * @return The unwrapped data or null if an error occurred */ public static byte[] unwrapAES(byte[] key, byte[] data, boolean hasnonce) { // // Initialize the AES Wrap Engine for unwrapping // AESWrapEngine aes = new AESWrapEngine(); KeyParameter keyparam = new KeyParameter(key); aes.init(false, keyparam); // // Unwrap then unpad data // try { byte[] unpadded = unpadPKCS7(aes.unwrap(data, 0, data.length)); // // Remove nonce if data has one // if (hasnonce) { return Arrays.copyOfRange(unpadded, OSS.NONCE_BYTES, unpadded.length); } else { return unpadded; } } catch (InvalidCipherTextException icte) { return null; } } public static byte[] unwrapAES(byte[] key, byte[] data) { return unwrapAES(key, data, false); } public static byte[] unwrapBlob(byte[] key, byte[] blob) { return unwrapAES(key, blob, true); } /** * Encrypt data using RSA. * CAUTION: this can take a while on large data * * @param key RSA key to use for encryption * @param data Cleartext data * @return The ciphertext data or null if an error occured */ public static byte[] encryptRSA(Key key, byte[] data) { // // Get an RSA Cipher instance // //Cipher rsa = null; try { /* The following commented code can be used the BouncyCastle * JCE provider signature is intact, which is not the * case when BC has been repackaged using jarjar rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC"); rsa.init (Cipher.ENCRYPT_MODE, key, CryptoHelper.sr); return rsa.doFinal(data); */ AsymmetricBlockCipher c = new PKCS1Encoding(new RSABlindedEngine()); if (key instanceof RSAPublicKey) { c.init(true, new RSAKeyParameters(true, ((RSAPublicKey) key).getModulus(), ((RSAPublicKey) key).getPublicExponent())); } else if (key instanceof RSAPrivateKey) { c.init(true, new RSAKeyParameters(true, ((RSAPrivateKey) key).getModulus(), ((RSAPrivateKey) key).getPrivateExponent())); } else { return null; } int insize = c.getInputBlockSize(); int offset = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while (offset < data.length) { int len = Math.min(insize, data.length - offset); baos.write(c.processBlock(data, offset, len)); offset += len; } return baos.toByteArray(); /* } catch (NoSuchProviderException nspe) { return null; } catch (NoSuchPaddingException nspe) { return null; } catch (NoSuchAlgorithmException nsae) { return null; } catch (InvalidKeyException ike) { return null; } catch (BadPaddingException bpe) { return null; } catch (IllegalBlockSizeException ibse) { return null; } */ } catch (InvalidCipherTextException icte) { return null; } catch (IOException ioe) { return null; } } /** * Decrypt data previously encrypted with RSA * @param key RSA key to use for decryption * @param data Ciphertext data * @return The cleartext data or null if an error occurred */ public static byte[] decryptRSA(Key key, byte[] data) { // // Get an RSA Cipher instance // //Cipher rsa = null; try { /* The following commented code can be used the BouncyCastle * JCE provider signature is intact, which is not the * case when BC has been repackaged using jarjar rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC"); rsa.init (Cipher.DECRYPT_MODE, key, CryptoHelper.sr); return rsa.doFinal(data); */ AsymmetricBlockCipher c = new PKCS1Encoding(new RSABlindedEngine()); if (key instanceof RSAPublicKey) { c.init(false, new RSAKeyParameters(true, ((RSAPublicKey) key).getModulus(), ((RSAPublicKey) key).getPublicExponent())); } else if (key instanceof RSAPrivateKey) { c.init(false, new RSAKeyParameters(true, ((RSAPrivateKey) key).getModulus(), ((RSAPrivateKey) key).getPrivateExponent())); } else { return null; } int insize = c.getInputBlockSize(); int offset = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while (offset < data.length) { int len = Math.min(insize, data.length - offset); baos.write(c.processBlock(data, offset, len)); offset += len; } return baos.toByteArray(); /* } catch (NoSuchProviderException nspe) { return null; } catch (NoSuchPaddingException nspe) { return null; } catch (NoSuchAlgorithmException nsae) { return null; } catch (InvalidKeyException ike) { return null; } catch (BadPaddingException bpe) { return null; } catch (IllegalBlockSizeException ibse) { return null; } */ } catch (InvalidCipherTextException icte) { return null; } catch (IOException ioe) { return null; } } /** * Sign data using the given algorithm * * @param algorithm Name of algorithm to use for signing * @param key Private key to use for signing (must be compatible with chosen algorithm) * @param data Data to sign * @return The signature of data or null if an error occurred */ public static byte[] sign(String algorithm, PrivateKey key, byte[] data) { try { Signature signature = Signature.getInstance(algorithm, "BC"); signature.initSign(key, CryptoHelper.sr); signature.update(data); return signature.sign(); } catch (SignatureException se) { return null; } catch (InvalidKeyException ike) { return null; } catch (NoSuchAlgorithmException nsae) { return null; } catch (NoSuchProviderException nspe) { return null; } } /** * Verify a signature * * @param algorithm Algorithm used to generate the signature * @param key Public key to use for verifying the signature * @param data Data whose signature must be verified * @param sig The signature to verify * @return true or false depending on successful verification */ public static boolean verify(String algorithm, PublicKey key, byte[] data, byte[] sig) { try { Signature signature = Signature.getInstance(algorithm, "BC"); signature.initVerify(key); signature.update(data); return signature.verify(sig); } catch (SignatureException se) { return false; } catch (InvalidKeyException ike) { return false; } catch (NoSuchAlgorithmException nsae) { return false; } catch (NoSuchProviderException nspe) { return false; } } /** * Convert an SSH Key Blob to a Public Key * * @param blob SSH Key Blob * @return The extracted public key or null if an error occurred */ public static PublicKey sshKeyBlobToPublicKey(byte[] blob) { // // RFC 4253 describes keys as either // // ssh-dss p q g y // ssh-rsa e n // // // Extract SSH key type // byte[] keyType = decodeNetworkString(blob, 0); int offset = 4 + keyType.length; String keyTypeStr = new String(keyType); try { if (SSH_DSS_PREFIX.equals(keyTypeStr)) { // // Extract DSA key parameters p q g and y // byte[] p = decodeNetworkString(blob, offset); offset += 4; offset += p.length; byte[] q = decodeNetworkString(blob, offset); offset += 4; offset += q.length; byte[] g = decodeNetworkString(blob, offset); offset += 4; offset += g.length; byte[] y = decodeNetworkString(blob, offset); offset += 4; offset += y.length; KeySpec key = new DSAPublicKeySpec(new BigInteger(y), new BigInteger(p), new BigInteger(q), new BigInteger(g)); return KeyFactory.getInstance("DSA").generatePublic(key); } else if (SSH_RSA_PREFIX.equals(keyTypeStr)) { // // Extract RSA key parameters e and n // byte[] e = decodeNetworkString(blob, offset); offset += 4; offset += e.length; byte[] n = decodeNetworkString(blob, offset); offset += 4; offset += n.length; KeySpec key = new RSAPublicKeySpec(new BigInteger(n), new BigInteger(e)); return KeyFactory.getInstance("RSA").generatePublic(key); } else { return null; } } catch (NoSuchAlgorithmException nsae) { nsae.printStackTrace(); return null; } catch (InvalidKeySpecException ikse) { ikse.printStackTrace(); return null; } } /** * Compute the MD5 fingerprint of an SSH key blob * * @param blob Public Key Blob to compute the fingerprint on * @return The computed signature or null if an error occurred */ public static byte[] sshKeyBlobFingerprint(byte[] blob) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(blob); return md5.digest(); } catch (NoSuchAlgorithmException nsae) { return null; } } /** * Encode a key pair as an SSH Private Key Blob * * @param kp Public/private key pair to encode * @return The encoded private key or null if provided key is not RSA or DSA */ public static byte[] sshPrivateKeyBlobFromKeyPair(KeyPair kp) { if (kp.getPrivate() instanceof RSAPrivateKey) { // // Extract key parameters // BigInteger n = ((RSAPublicKey) kp.getPublic()).getModulus(); BigInteger e = ((RSAPublicKey) kp.getPublic()).getPublicExponent(); BigInteger d = ((RSAPrivateKey) kp.getPrivate()).getPrivateExponent(); // Not available and not used by ssh-agent anyway ... BigInteger iqmp = BigInteger.ZERO; BigInteger p = BigInteger.ZERO; BigInteger q = BigInteger.ZERO; byte[] tns = null; try { tns = encodeNetworkString(SSH_RSA_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] nns = encodeNetworkString(n.toByteArray()); byte[] ens = encodeNetworkString(e.toByteArray()); byte[] dns = encodeNetworkString(d.toByteArray()); byte[] iqmpns = encodeNetworkString(iqmp.toByteArray()); byte[] pns = encodeNetworkString(p.toByteArray()); byte[] qns = encodeNetworkString(q.toByteArray()); // // Allocate array for blob // byte[] blob = new byte[tns.length + nns.length + ens.length + dns.length + iqmpns.length + pns.length + qns.length]; // // Copy network strings to blob // System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(nns, 0, blob, tns.length, nns.length); System.arraycopy(ens, 0, blob, tns.length + nns.length, ens.length); System.arraycopy(dns, 0, blob, tns.length + nns.length + ens.length, dns.length); System.arraycopy(iqmpns, 0, blob, tns.length + nns.length + ens.length + dns.length, iqmpns.length); System.arraycopy(pns, 0, blob, tns.length + nns.length + ens.length + dns.length + iqmpns.length, pns.length); System.arraycopy(qns, 0, blob, tns.length + nns.length + ens.length + dns.length + iqmpns.length + pns.length, qns.length); return blob; } else if (kp.getPrivate() instanceof DSAPrivateKey) { // // Extract key parameters // BigInteger p = ((DSAPublicKey) kp.getPublic()).getParams().getP(); BigInteger q = ((DSAPublicKey) kp.getPublic()).getParams().getQ(); BigInteger g = ((DSAPublicKey) kp.getPublic()).getParams().getG(); BigInteger y = ((DSAPublicKey) kp.getPublic()).getY(); BigInteger x = ((DSAPrivateKey) kp.getPrivate()).getX(); // // Encode parameters as network strings // byte[] tns = null; try { tns = encodeNetworkString(SSH_DSS_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] pns = encodeNetworkString(p.toByteArray()); byte[] qns = encodeNetworkString(q.toByteArray()); byte[] gns = encodeNetworkString(g.toByteArray()); byte[] yns = encodeNetworkString(y.toByteArray()); byte[] xns = encodeNetworkString(x.toByteArray()); // // Allocate array for blob // byte[] blob = new byte[tns.length + pns.length + qns.length + gns.length + yns.length + xns.length]; // // Copy network strings to blob // System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(pns, 0, blob, tns.length, pns.length); System.arraycopy(qns, 0, blob, tns.length + pns.length, qns.length); System.arraycopy(gns, 0, blob, tns.length + pns.length + qns.length, gns.length); System.arraycopy(yns, 0, blob, tns.length + pns.length + qns.length + gns.length, yns.length); System.arraycopy(xns, 0, blob, tns.length + pns.length + qns.length + gns.length + yns.length, xns.length); return blob; } else { return null; } } /** * Encode a public key as an SSH Key Blob * * @param key Public key to encode * @return The encoded public key or null if provided key is not RSA or DSA */ public static byte[] sshKeyBlobFromPublicKey(PublicKey key) { if (key instanceof RSAPublicKey) { // // Extract public exponent and modulus // BigInteger e = ((RSAPublicKey) key).getPublicExponent(); BigInteger n = ((RSAPublicKey) key).getModulus(); // // Encode parameters as Network Strings // byte[] tns = null; try { tns = encodeNetworkString(SSH_RSA_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] ens = encodeNetworkString(e.toByteArray()); byte[] nns = encodeNetworkString(n.toByteArray()); // // Allocate array for blob // byte[] blob = new byte[tns.length + nns.length + ens.length]; // // Copy network strings to blob // System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(ens, 0, blob, tns.length, ens.length); System.arraycopy(nns, 0, blob, tns.length + ens.length, nns.length); return blob; } else if (key instanceof DSAPublicKey) { // // Extract key parameters // BigInteger p = ((DSAPublicKey) key).getParams().getP(); BigInteger q = ((DSAPublicKey) key).getParams().getQ(); BigInteger g = ((DSAPublicKey) key).getParams().getG(); BigInteger y = ((DSAPublicKey) key).getY(); // // Encode parameters as network strings // byte[] tns = null; try { tns = encodeNetworkString(SSH_DSS_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] pns = encodeNetworkString(p.toByteArray()); byte[] qns = encodeNetworkString(q.toByteArray()); byte[] gns = encodeNetworkString(g.toByteArray()); byte[] yns = encodeNetworkString(y.toByteArray()); // // Allocate array for blob // byte[] blob = new byte[tns.length + pns.length + qns.length + gns.length + yns.length]; // // Copy network strings to blob // System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(pns, 0, blob, tns.length, pns.length); System.arraycopy(qns, 0, blob, tns.length + pns.length, qns.length); System.arraycopy(gns, 0, blob, tns.length + pns.length + qns.length, gns.length); System.arraycopy(yns, 0, blob, tns.length + pns.length + qns.length + gns.length, yns.length); return blob; } else { return null; } } /** * Generate an SSH signature blob * * @param data Data to sign * @param key Private key to use for signing * @return The generated signature blob or null if the provided key is not supported */ public static byte[] sshSignatureBlobSign(byte[] data, PrivateKey key) { try { if (key instanceof RSAPrivateKey) { // // Create Signature object // Signature signature = java.security.Signature.getInstance("SHA1withRSA"); signature.initSign(key); signature.update(data); byte[] sig = signature.sign(); // // Build the SSH sigBlob // byte[] tns = null; try { encodeNetworkString(SSH_RSA_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] sns = encodeNetworkString(sig); byte[] blob = new byte[tns.length + sns.length]; System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(sns, 0, blob, tns.length, sns.length); return blob; } else if (key instanceof DSAPrivateKey) { // // Create Signature object // Signature signature = java.security.Signature.getInstance("SHA1withDSA"); signature.initSign(key); signature.update(data); byte[] asn1sig = signature.sign(); // // Convert ASN.1 signature to SSH signature blob // Inspired by OpenSSH code // int frst = asn1sig[3] - (byte) 0x14; int scnd = asn1sig[1] - (byte) 0x2c - frst; byte[] sshsig = new byte[asn1sig.length - frst - scnd - 6]; System.arraycopy(asn1sig, 4 + frst, sshsig, 0, 20); System.arraycopy(asn1sig, 6 + asn1sig[3] + scnd, sshsig, 20, 20); byte[] tns = null; try { tns = encodeNetworkString(SSH_DSS_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) { } byte[] sns = encodeNetworkString(sshsig); byte[] blob = new byte[tns.length + sns.length]; System.arraycopy(tns, 0, blob, 0, tns.length); System.arraycopy(sns, 0, blob, tns.length, sns.length); return blob; } else { return null; } } catch (NoSuchAlgorithmException nsae) { return null; } catch (InvalidKeyException ike) { return null; } catch (SignatureException se) { return null; } } /** * Verify the signature included in an SSH signature blob * * @param data The data whose signature must be verified * @param off Offset in the data array * @param len Length of data to sign * @param sigBlob The SSH signature blob containing the signature * @param pubkey The public key to use to verify the signature * @return true if the signature was verified successfully, false in all other cases (including if an error occurs). */ public static boolean sshSignatureBlobVerify(byte[] data, int off, int len, byte[] sigBlob, PublicKey pubkey) { Signature signature = null; int offset = 0; byte[] sigType = decodeNetworkString(sigBlob, 0); offset += 4; offset += sigType.length; String sigTypeStr = new String(sigType); try { if (pubkey instanceof RSAPublicKey && SSH_RSA_PREFIX.equals(sigTypeStr)) { // // Create Signature object // signature = java.security.Signature.getInstance("SHA1withRSA"); signature.initVerify(pubkey); signature.update(data, off, len); byte[] sig = decodeNetworkString(sigBlob, offset); return signature.verify(sig); } else if (pubkey instanceof DSAPublicKey && SSH_DSS_PREFIX.equals(sigTypeStr)) { // // Create Signature object // signature = java.security.Signature.getInstance("SHA1withDSA"); signature.initVerify(pubkey); signature.update(data, off, len); // // Convert SSH signature blob to ASN.1 signature // byte[] rs = decodeNetworkString(sigBlob, offset); // ASN.1 int frst = ((rs[0] & 0x80) != 0 ? 1 : 0); int scnd = ((rs[20] & 0x80) != 0 ? 1 : 0); int length = rs.length + 6 + frst + scnd; byte[] asn1sig = new byte[length]; asn1sig[0] = (byte) 0x30; asn1sig[1] = (byte) 0x2c; asn1sig[1] += frst; asn1sig[1] += scnd; asn1sig[2] = (byte) 0x02; asn1sig[3] = (byte) 0x14; asn1sig[3] += frst; System.arraycopy(rs, 0, asn1sig, 4 + frst, 20); asn1sig[4 + asn1sig[3]] = (byte) 0x02; asn1sig[5 + asn1sig[3]] = (byte) 0x14; asn1sig[5 + asn1sig[3]] += scnd; System.arraycopy(rs, 20, asn1sig, 6 + asn1sig[3] + scnd, 20); // // Verify signature // return signature.verify(asn1sig); } else { return false; } } catch (NoSuchAlgorithmException nsae) { return false; } catch (SignatureException se) { return false; } catch (InvalidKeyException ike) { return false; } } public static boolean sshSignatureBlobVerify(byte[] data, byte[] sigBlob, PublicKey pubkey) { return sshSignatureBlobVerify(data, 0, data.length, sigBlob, pubkey); } /** * Extract an encoded Network String * A Network String has its length on 4 bytes (MSB first). * * @param data Data to parse * @param offset Offset at which the network string starts. * @return */ public static byte[] decodeNetworkString(byte[] data, int offset) { int len = unpackInt(data, offset); // // Safety net, don't allow to allocate more than // what's left in the array // if (len > data.length - offset - 4) { return null; } byte[] string = new byte[len]; System.arraycopy(data, offset + 4, string, 0, len); return string; } /** * Encode data as a Network String * A Network String has its length on 4 bytes (MSB first). * * @param data Data to encode * @return the encoded data */ public static byte[] encodeNetworkString(byte[] data) { byte[] ns = new byte[4 + data.length]; // // Pack data length // packInt(data.length, ns, 0); System.arraycopy(data, 0, ns, 4, data.length); return ns; } /** * Pack an integer value in a byte array, MSB first * * @param value Value to pack * @param data Byte array where to pack * @param offset Offset where to start */ private static void packInt(int value, byte[] data, int offset) { data[0] = (byte) ((value >> 24) & 0x000000ff); data[1] = (byte) ((value >> 16) & 0x000000ff); data[2] = (byte) ((value >> 8) & 0x000000ff); data[3] = (byte) (value & 0x000000ff); } /** * Unpack an int stored as MSB first in a byte array * @param data Array from which to extract the int * @param offset Offset in the array where the int is stored * @return */ private static int unpackInt(byte[] data, int offset) { int value = 0; value |= (data[offset] << 24) & 0xff000000; value |= (data[offset + 1] << 16) & 0x00ff0000; value |= (data[offset + 2] << 8) & 0x0000ff00; value |= data[offset + 3] & 0x000000ff; return value; } public static class SSHAgentClient { // // Request / Response codes from OpenSSH implementation // Some are not supported (yet) by this implementation // //private static final int AGENTC_REQUEST_RSA_IDENTITIES = 1; //private static final int AGENT_RSA_IDENTITIES_ANSWER = 2; private static final int AGENT_FAILURE = 5; private static final int AGENT_SUCCESS = 6; //private static final int AGENTC_REMOVE_RSA_IDENTITY = 8; //private static final int AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9; private static final int AGENTC_REQUEST_IDENTITIES = 11; private static final int AGENT_IDENTITIES_ANSWER = 12; private static final int AGENTC_SIGN_REQUEST = 13; private static final int AGENT_SIGN_RESPONSE = 14; private static final int AGENTC_ADD_IDENTITY = 17; //private static final int AGENTC_REMOVE_IDENTITY = 18; //private static final int AGENTC_REMOVE_ALL_IDENTITIES = 19; private static boolean hasJUDS = false; static { try { Class c = Class.forName("com.etsy.net.UnixDomainSocket"); hasJUDS = true; } catch (Throwable t) { hasJUDS = false; } finally { if (!hasJUDS) { System.err.println( "No JUDS support, please use -Djuds.in=... -Djuds.out=... or -Djuds.addr=... -Djuds.port=..."); } } } private UnixDomainSocketClient socket = null; private Socket sock = null; private InputStream in = null; private OutputStream out = null; private ByteArrayOutputStream buffer = null; /** * Callback interface to handle agent response */ private static interface AgentCallback { public Object onSuccess(byte[] packet); public Object onFailure(byte[] packet); } public static class SSHKey { public byte[] blob; public String comment; public String fingerprint; public String toString() { StringBuilder sb = new StringBuilder(); sb.append(fingerprint); sb.append(" "); sb.append(comment); return sb.toString(); } } /** * Create an instance of SSHAgentClient using the Unix Socket defined in * the environment variable SSH_AUTH_SOCK as set by ssh-agent * * @throws IOException in case of errors */ public SSHAgentClient() throws IOException { this(System.getenv("SSH_AUTH_SOCK")); } /** * Create an instance of SSHAgentClient using the provided Unix Socket * * @param path Path to the Unix domain socket to use. * @throws IOException In case of errors */ public SSHAgentClient(String path) throws IOException { if (null != System.getProperty("juds.in") && null != System.getProperty("juds.out")) { in = new FileInputStream(System.getProperty("juds.in")); out = new FileOutputStream(System.getProperty("juds.out")); } else if (null != System.getProperty("juds.addr") && null != System.getProperty("juds.port")) { sock = new Socket(); SocketAddress endpoint = new InetSocketAddress(System.getProperty("juds.addr"), Integer.valueOf(System.getProperty("juds.port"))); sock.connect(endpoint); in = sock.getInputStream(); out = sock.getOutputStream(); } else if (hasJUDS) { // // Connect to the local socket of the SSH agent // socket = new UnixDomainSocketClient(path, JUDS.SOCK_STREAM); in = socket.getInputStream(); out = socket.getOutputStream(); } else { throw new RuntimeException( "No JUDS Support, use -Djuds.in=... -Djuds.out=... or -Djuds.addr=... -Djuds.port=..."); } // // Create an input buffer for data exchange with the socket // buffer = new ByteArrayOutputStream(); } public void close() { if (null != sock) { try { sock.close(); } catch (IOException ioe) { } } else if (null != socket) { socket.close(); } else { try { in.close(); } catch (IOException ioe) { } try { out.close(); } catch (IOException ioe) { } } } /** * Send a request to the SSH Agent * * @param type Type of request to send * @param data Data packet of the request * @throws IOException in case of errors */ private void sendRequest(int type, byte[] data) throws IOException { // // Allocate request packet. // It needs to hold the request data, the data length // and the request type. // byte[] packet = new byte[data.length + 4 + 1]; // // Pack data length + 1 (request type) // packInt(data.length + 1, packet, 0); // // Store request type // packet[4] = (byte) type; // // Copy request data // System.arraycopy(data, 0, packet, 5, data.length); // // Write request packet onto the socket // out.write(packet); out.flush(); } /** * Listen to agent response and call the appropriate method * of the provided callback. * * @param callback * @return * @throws IOException */ private Object awaitResponse(AgentCallback callback) throws IOException { int packetLen = -1; byte[] buf = new byte[128]; while (true) { int len = in.read(buf); // // Add data to buffer // if (len > 0) { buffer.write(buf, 0, len); } // // If buffer contains less than 4 bytes, continue reading data // if (buffer.size() <= 4) { continue; } // // If packet len has not yet been extracted, read it. // if (packetLen < 0) { packetLen = unpackInt(buffer.toByteArray(), 0); } // // If buffer does not the full packet yet, continue reading // if (buffer.size() < 4 + packetLen) { continue; } // // Buffer contains the packet data, // convert input buffer to byte array // byte[] inbuf = buffer.toByteArray(); // // Extract packet data // byte[] packet = new byte[packetLen]; System.arraycopy(inbuf, 4, packet, 0, packetLen); // // Put extraneous data at the beginning of 'buffer' // buffer.reset(); buffer.write(inbuf, 4 + packetLen, inbuf.length - packetLen - 4); // // Extract response type // int respType = packet[0]; if (AGENT_FAILURE == respType) { return callback.onFailure(packet); } else if (AGENT_SUCCESS == respType) { return callback.onSuccess(new byte[0]); } else { return callback.onSuccess(packet); } } } /** * Add an identity to the agent * * @param keyblob SSH key blob * @param comment A comment to describe the identity * @return true if the identity has been succesfully loaded */ public boolean addIdentity(byte[] keyblob, String comment) throws IOException { ByteArrayOutputStream request = new ByteArrayOutputStream(); request.write(keyblob); request.write(encodeNetworkString(comment.getBytes())); sendRequest(AGENTC_ADD_IDENTITY, request.toByteArray()); return (Boolean) awaitResponse(new AgentCallback() { @Override public Object onFailure(byte[] packet) { return false; } @Override public Object onSuccess(byte[] packet) { return true; } }); } /** * Request the agent to sign 'data' using the provided key blob * * @param keyblob SSH Key Blob * @param data Data to sign * @return An SSH signature blob */ public byte[] sign(byte[] keyblob, byte[] data) throws IOException { // // Create request packet // ByteArrayOutputStream request = new ByteArrayOutputStream(); request.write(encodeNetworkString(keyblob)); request.write(encodeNetworkString(data)); request.write(new byte[4]); sendRequest(AGENTC_SIGN_REQUEST, request.toByteArray()); return (byte[]) awaitResponse(new AgentCallback() { @Override public Object onFailure(byte[] packet) { return null; } @Override public Object onSuccess(byte[] packet) { if (AGENT_SIGN_RESPONSE != packet[0]) { return null; } byte[] signature = decodeNetworkString(packet, 1); return signature; } }); } public List<SSHKey> requestIdentities() throws IOException { sendRequest(AGENTC_REQUEST_IDENTITIES, new byte[0]); Object result = awaitResponse(new AgentCallback() { @Override public Object onFailure(byte[] packet) { return null; } @Override public Object onSuccess(byte[] packet) { if (AGENT_IDENTITIES_ANSWER != packet[0]) { return null; } List<SSHKey> keys = new ArrayList<SSHKey>(); int offset = 1; int numKeys = unpackInt(packet, offset); offset += 4; for (int i = 0; i < numKeys; i++) { SSHKey key = new SSHKey(); // // Extract key blob // key.blob = decodeNetworkString(packet, offset); offset += 4 + key.blob.length; // // Extract comment // byte[] comment = decodeNetworkString(packet, offset); key.comment = new String(comment); offset += 4 + comment.length; // // Compute key fingerprint // try { key.fingerprint = new String(Hex.encode(sshKeyBlobFingerprint(key.blob)), "UTF-8"); } catch (UnsupportedEncodingException uee) { } keys.add(key); } return keys; } }); return (List<SSHKey>) result; } } // // Shamir Secret Sharing Scheme // The following code is inspired by a java version of Tontine // /** * Abstract class representing a number */ private static abstract class SSSSNumber extends Number { public abstract SSSSNumber add(SSSSNumber b); public abstract SSSSNumber sub(SSSSNumber b); public abstract SSSSNumber mul(SSSSNumber b); public abstract SSSSNumber div(SSSSNumber b); public abstract SSSSNumber neg(); public abstract SSSSNumber zero(); public abstract SSSSNumber one(); public abstract SSSSNumber clone(); } /** * Subclass of SSSSNumber for integer type */ private static final class SSSSInt extends SSSSNumber { private int value; public SSSSInt(int v) { value = v; } public SSSSNumber add(SSSSNumber b) { if (b instanceof SSSSInt) { SSSSInt bInt = (SSSSInt) b; return new SSSSInt(value + bInt.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber sub(SSSSNumber b) { if (b instanceof SSSSInt) { SSSSInt bInt = (SSSSInt) b; return new SSSSInt(value - bInt.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber mul(SSSSNumber b) { if (b instanceof SSSSInt) { SSSSInt bInt = (SSSSInt) b; return new SSSSInt(value * bInt.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber div(SSSSNumber b) { if (b instanceof SSSSInt) { SSSSInt bInt = (SSSSInt) b; return new SSSSInt(value / bInt.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber neg() { return new SSSSInt(-value); } public SSSSNumber zero() { return new SSSSInt(0); } public SSSSNumber one() { return new SSSSInt(1); } public int intValue() { return (int) value; } public long longValue() { return (long) value; } public float floatValue() { return (float) value; } public double doubleValue() { return (double) value; } public boolean equals(Object o) { if (o instanceof SSSSInt) { return (value == ((SSSSInt) o).value); } else { return false; } } public String toString() { return Integer.toString(value); } public SSSSInt clone() { return new SSSSInt(this.intValue()); } } /** * Subclass of SSSSNumber for double types */ private static final class SSSSDouble extends SSSSNumber { private double value; public SSSSDouble(double v) { this.value = v; } public SSSSNumber add(SSSSNumber b) { if (b instanceof SSSSDouble) { SSSSDouble bDouble = (SSSSDouble) b; return new SSSSDouble(value + bDouble.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber sub(SSSSNumber b) { if (b instanceof SSSSDouble) { SSSSDouble bDouble = (SSSSDouble) b; return new SSSSDouble(value - bDouble.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber mul(SSSSNumber b) { if (b instanceof SSSSDouble) { SSSSDouble bDouble = (SSSSDouble) b; return new SSSSDouble(value * bDouble.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber div(SSSSNumber b) { if (b instanceof SSSSDouble) { SSSSDouble bDouble = (SSSSDouble) b; return new SSSSDouble(value / bDouble.value); } else { throw new RuntimeException("Type mismatch"); } } public SSSSNumber neg() { return new SSSSDouble(-value); } public SSSSNumber zero() { return new SSSSDouble(0); } public SSSSNumber one() { return new SSSSDouble(1); } public int intValue() { return (int) value; } public long longValue() { return (long) value; } public float floatValue() { return (float) value; } public double doubleValue() { return (double) value; } public boolean equals(Object o) { if (o instanceof SSSSDouble) { return (value == ((SSSSDouble) o).value); } else { return false; } } public String toString() { return Double.toString(value); } public SSSSDouble clone() { return new SSSSDouble(this.doubleValue()); } } /** * Subclass of SSSSNumber for polynomials */ private static final class SSSSPolynomial extends SSSSNumber { /** * Polynomial coefficients */ SSSSNumber[] coefficients; public SSSSPolynomial(SSSSNumber[] c) { this.coefficients = new SSSSNumber[c.length]; for (int i = 0; i < c.length; i++) { if (null == c[i]) { throw new RuntimeException("Null coefficient for degree " + i); } this.coefficients[i] = c[i].clone(); } } /** * Compute the value of polynomial at x * @param x * @return */ public SSSSNumber f(SSSSNumber x) { SSSSNumber result = coefficients[coefficients.length - 1]; for (int i = coefficients.length - 1; i > 0; i--) { result = result.mul(x); result = result.add(coefficients[i - 1]); } return result; } public SSSSNumber add(SSSSNumber b) { SSSSNumber[] result; if (b instanceof SSSSPolynomial) { SSSSPolynomial bPoly = (SSSSPolynomial) b; int degMin = Math.min(coefficients.length, bPoly.coefficients.length); int degMax = Math.max(coefficients.length, bPoly.coefficients.length); boolean bBigger = (bPoly.coefficients.length > coefficients.length); result = new SSSSNumber[degMax]; for (int i = 0; i < degMin; i++) { result[i] = coefficients[i].add(bPoly.coefficients[i]); } for (int i = degMin; i < degMax; i++) { if (bBigger) { result[i] = bPoly.coefficients[i]; } else { result[i] = coefficients[i]; } } } else { result = copy(); result[0].add(b); } return new SSSSPolynomial(result); } public SSSSNumber sub(SSSSNumber b) { SSSSNumber[] result; if (b instanceof SSSSPolynomial) { SSSSPolynomial bPoly = (SSSSPolynomial) b; int degMin = Math.min(coefficients.length, bPoly.coefficients.length); int degMax = Math.max(coefficients.length, bPoly.coefficients.length); boolean bBigger = (bPoly.coefficients.length > coefficients.length); result = new SSSSNumber[degMax]; for (int i = 0; i < degMin; i++) { result[i] = coefficients[i].sub(bPoly.coefficients[i]); } for (int i = degMin; i < degMax; i++) { if (bBigger) { result[i] = bPoly.coefficients[i].neg(); } else { result[i] = coefficients[i]; } } } else { result = copy(); result[0].add(b); } return new SSSSPolynomial(result); } public SSSSNumber mul(SSSSNumber b) { SSSSNumber[] result; if (b instanceof SSSSPolynomial) { SSSSPolynomial bPoly = (SSSSPolynomial) b; result = new SSSSNumber[coefficients.length + bPoly.coefficients.length - 1]; for (int i = 0; i < coefficients.length; i++) { for (int j = 0; j < bPoly.coefficients.length; j++) { SSSSNumber co = coefficients[i].mul(bPoly.coefficients[j]); if (result[i + j] == null) { result[i + j] = co; } else { result[i + j] = result[i + j].add(co); } } } } else { result = copy(); for (int i = 0; i < result.length; i++) { result[i] = result[i].mul(b); } } return new SSSSPolynomial(result); } public SSSSNumber div(SSSSNumber b) { return null; } public SSSSNumber neg() { SSSSNumber[] result = copy(); for (int i = 0; i < result.length; i++) { result[i] = result[i].neg(); } return new SSSSPolynomial(result); } public SSSSNumber zero() { return coefficients[0].zero(); } public SSSSNumber one() { return coefficients[0].one(); } public int getDegree() { if (coefficients.length > 0) { return coefficients.length - 1; } else { return 0; } } public SSSSNumber getCoefficient(int index) { return coefficients[index]; } public int intValue() { return coefficients[0].intValue(); } public long longValue() { return coefficients[0].longValue(); } public float floatValue() { return coefficients[0].floatValue(); } public double doubleValue() { return coefficients[0].doubleValue(); } private SSSSNumber[] copy() { SSSSNumber[] result = new SSSSNumber[coefficients.length]; System.arraycopy(coefficients, 0, result, 0, coefficients.length); return result; } private int degree(SSSSNumber[] coeff) { if (null == coeff) { return 0; } for (int i = coeff.length - 1; i >= 0; i--) { if (!coeff[i].equals(coeff[i].zero())) { return i; } } return 0; } public String toString() { StringBuilder result = new StringBuilder(); for (int i = getDegree(); i >= 0; i--) { result.append(getCoefficient(i)); result.append("*x^"); result.append(i); result.append(' '); } return result.toString(); } public SSSSPolynomial clone() { return new SSSSPolynomial(this.coefficients); } } /** * A point has two coordinates (x,y) of type {@link SSSSNumber}. */ private static final class SSSSXY extends SSSSNumber { SSSSNumber x; SSSSNumber y; /** * Create a new point. * * @param x The x coordinate of this point. * @param y The y coordinate of this point. */ public SSSSXY(SSSSNumber x, SSSSNumber y) { this.x = x; this.y = y; } public SSSSNumber add(SSSSNumber b) { if (b instanceof SSSSXY) { SSSSXY bXY = (SSSSXY) b; return new SSSSXY(getX().add(bXY.getX()), getY().add(bXY.getY())); } else { throw new UnsupportedOperationException("Need to add an instance of SSSSXY."); } } public SSSSNumber sub(SSSSNumber b) { if (b instanceof SSSSXY) { SSSSXY bXY = (SSSSXY) b; return new SSSSXY(getX().sub(bXY.getX()), getY().sub(bXY.getY())); } else { throw new UnsupportedOperationException("Need to add an instance of SSSSXY."); } } /** * Multiplication of two XY is defined as a new XY whose coordinates * are the product of each member's matching coordinates */ public SSSSNumber mul(SSSSNumber b) { if (b instanceof SSSSXY) { SSSSXY bXY = (SSSSXY) b; return new SSSSXY(getX().mul(bXY.getX()), getY().mul(bXY.getY())); } else { return new SSSSXY(getX().mul(b), getY().mul(b)); } } /** * Division of two XY is defined as a new XY whose coordinates are * the results of the division of each dividend's coordinate by the matching coordinate of the * divisor */ public SSSSNumber div(SSSSNumber b) { if (b instanceof SSSSXY) { SSSSXY bXY = (SSSSXY) b; return new SSSSXY(getX().div(bXY.getX()), getY().div(bXY.getY())); } else { return new SSSSXY(getX().div(b), getY().div(b)); } } public SSSSNumber neg() { return new SSSSXY(getX().neg(), getY().neg()); } public SSSSNumber zero() { return new SSSSXY(getX().zero(), getY().zero()); } public SSSSNumber one() { return new SSSSXY(getX().one(), getY().one()); } public SSSSNumber getX() { return this.x; } public SSSSNumber getY() { return this.y; } public int intValue() { throw new UnsupportedOperationException(); } public long longValue() { throw new UnsupportedOperationException(); } public float floatValue() { throw new UnsupportedOperationException(); } public double doubleValue() { throw new UnsupportedOperationException(); } public SSSSXY clone() { return new SSSSXY(this.x, this.y); } } /** * This exception is throw when two points with the same x coordinate are encountered in * an {@link SSSSPolynomialInterpolator}. */ private static class SSSSDuplicateAbscissaException extends Exception { public SSSSDuplicateAbscissaException() { super("Abscissa collision detected during interpolation"); } } /** * Interface of polynomial interpolator */ private static interface SSSSPolynomialInterpolator { /** * Find a polynomial that interpolates the given points. * * @param points Set of points to interpolate * @return The polynomial passing through the given points. * @throws SSSSDuplicateAbscissaException If any two points share the same x coordinate. */ public SSSSPolynomial interpolate(SSSSXY[] points) throws SSSSDuplicateAbscissaException; } /** * Polynomial interpolator using Lagrange polynomials * * @see http://en.wikipedia.org/wiki/Lagrange_polynomial */ private static class SSSSLagrangePolynomialInterpolator implements SSSSPolynomialInterpolator { public SSSSPolynomial interpolate(SSSSXY[] points) throws SSSSDuplicateAbscissaException { SSSSNumber result = null; // // Check x coordinates // checkPointSeparation(points); // // Build the interpolating polynomial as a linear combination // of Lagrange basis polynomials // for (int j = 0; j < points.length; j++) { if (result != null) { result = result.add(Lj(points, j)); } else { result = Lj(points, j); } } return (SSSSPolynomial) result; } /** * Checks that a set of points does not contain points with identical x coordinates. * * @param points Set of points to check. * @throws SSSSDuplicateAbscissaException if identical x coordinates exist */ private void checkPointSeparation(SSSSXY[] points) throws SSSSDuplicateAbscissaException { for (int i = 0; i < points.length - 1; i++) { for (int j = i + 1; j < points.length; j++) { if (points[i].getX().equals(points[j].getX())) { throw new SSSSDuplicateAbscissaException(); } } } } /** * Return the j'th Lagrange basis polynomial * * @param points Set of points to interpolate * @param j Index of polynomial to return * @return */ private SSSSNumber Lj(SSSSXY[] points, int j) { SSSSNumber one = points[0].getX().one(); SSSSNumber[] resultP = new SSSSNumber[1]; resultP[0] = points[j].getY(); SSSSNumber[] product = new SSSSNumber[2]; SSSSNumber result = new SSSSPolynomial(resultP); for (int i = 0; i < points.length; i++) { if (i == j) { continue; } SSSSNumber numerator; SSSSNumber denominator; numerator = one; denominator = points[j].getX().sub(points[i].getX()); product[1] = numerator.div(denominator); numerator = points[i].getX(); denominator = points[i].getX().sub(points[j].getX()); product[0] = numerator.div(denominator); SSSSPolynomial poly = new SSSSPolynomial(product); result = result.mul(poly); } return result; } } /** * This class represents polynomials over GF(256) * * GF(p^n) is a finite (or Galois) field, p being prime. * * @see http://mathworld.wolfram.com/FiniteField.html * * The advantage of GF(256) is that it contains 256 elements * so each byte value can be considered a polynomial over GF(256) * and arithmetic rules can be implemented that represent polynomial * arithmetic in GF(256). * * Each byte is mapped to a polynomial using the following rule: * * Bit n of a byte is the polynomial coefficient of x^n. * * So 0x0D = 0b00001101 = x^3 + x^2 + 1 * * The modulo polynomial used to generate this field is * * x^8 + x^4 + x^3 + x^2 + 1 * * 0x011D = 0b0000000100011101 */ public static final class SSSSGF256Polynomial extends SSSSNumber { /** * Value representing the polynomial */ private short value; /** * Generator used to generate the exp/log tables * * For QR/Code generator is 0x02 and Prime polynomial 0x11d * For Rijndael generator is 0x03 and Prime polynomial 0x11b */ private static final int GF256_GENERATOR = 0x02; /** * Prime polynomial used to generate the exp/log tables */ private static final int GF256_PRIME_POLYNOMIAL = 0x11d; public static final short[] GF256_exptable; public static final short[] GF256_logtable; static { // // Generate GF256 exp/log tables // @see http://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders // @see http://www.samiam.org/galois.html // GF256_exptable = new short[256]; GF256_logtable = new short[256]; GF256_logtable[0] = (1 - 256) & 0xff; GF256_exptable[0] = 1; for (int i = 1; i < 256; i++) { int exp = GF256_exptable[i - 1] * GF256_GENERATOR; if (exp >= 256) { exp ^= GF256_PRIME_POLYNOMIAL; } exp &= 0xff; GF256_exptable[i] = (short) exp; // Generator^255 = Generator^0 so we use the power modulo 255 // @see http://math.stackexchange.com/questions/76045/reed-solomon-polynomial-generator GF256_logtable[GF256_exptable[i]] = (short) (((short) i) % 255); } } /** * @param v The integer representing the polynomial in this * field. A bit i = 2<sup>n</sup> is set iff * x<sup>n</sup> is a term in the polynomial. */ public SSSSGF256Polynomial(int v) { value = (short) v; } /** * Implement addition on GF256, polynomial coefficients are * XORed * * @param b The SSSSGF256Polynomial to add * @throws RuntimeException in case of type mismatch */ public SSSSNumber add(SSSSNumber b) { if (b instanceof SSSSGF256Polynomial) { SSSSGF256Polynomial bPoly = (SSSSGF256Polynomial) b; return new SSSSGF256Polynomial(bPoly.value ^ value); } else { throw new RuntimeException("Type mismatch"); } } /** * Polynomial subtraction is really an addition since in * GF(2^n), a + b = a - b * * @param b SSSSGF256Polynomial to subtract * @throws RuntimeException in case of type mismatch */ public SSSSNumber sub(SSSSNumber b) { return add(b); } /** * Multiplication in GF256. * * @param b The second term. It must also be a GF256. * @throws RuntimeException If the second term is not of type * GF256. */ public SSSSNumber mul(SSSSNumber b) { if (b instanceof SSSSGF256Polynomial) { SSSSGF256Polynomial bPoly = (SSSSGF256Polynomial) b; // // Handle the special case of 0 // 0 * x = x * 0 = 0 // if ((bPoly.value == 0) || (value == 0)) { return zero(); } // // In the general case, multiplication is done using logarithms // @see http://www.logic.at/wiki/index.php/GF(256) // // log(a * b) = log(a) + log(b) // // Modulo is 255 because there are only 255 values in the log table int newPower = (log() + bPoly.log()) % 255; return new SSSSGF256Polynomial(GF256_exptable[newPower]); } else { throw new RuntimeException("Type mismatch."); } } /** * Division in GF256. * * @param b The second term. It must also be a GF256. * @throws RuntimeException If the second term is not of type * GF256 or if we divide by 0. */ public SSSSNumber div(SSSSNumber b) { if (b instanceof SSSSGF256Polynomial) { SSSSGF256Polynomial bPoly = (SSSSGF256Polynomial) b; // // Cannot divide by 0 // if (bPoly.value == 0) { throw new RuntimeException("Division by zero."); } // // 0 / x = 0 // if (value == 0) { return zero(); } // // Use the log rule: // @see http://www.logic.at/wiki/index.php/GF(256) // // log(a/b) = log(a) - log(b) // int newPower = (log() - bPoly.log()) % 255; if (newPower < 0) { newPower += 255; } return (SSSSNumber) new SSSSGF256Polynomial(GF256_exptable[newPower]); } else { throw new RuntimeException("Type mismatch"); } } /** * a + a = 0, so a = -a. * * @return The arithmetic inverse of the current value. */ public SSSSNumber neg() { return new SSSSGF256Polynomial(-value); } /** * @return The arithmetic identity. */ public SSSSNumber zero() { return new SSSSGF256Polynomial(0); } /** * @return The multiplicative identity. */ public SSSSNumber one() { return new SSSSGF256Polynomial(1); } public int intValue() { return (int) value; } public long longValue() { return (long) value; } public float floatValue() { return (float) value; } public double doubleValue() { return (double) value; } /** * @return The value n such that x<sup>n</n> is equivalent to * the current polynomial in this field. */ private int log() { if (0 == value) { throw new RuntimeException("Cannot take log of 0"); } return GF256_logtable[value]; } public String toString() { return Short.toString(value); } public int hashCode() { return value; } public boolean equals(Object o) { if (o instanceof SSSSGF256Polynomial) { SSSSGF256Polynomial oPoly = (SSSSGF256Polynomial) o; if (oPoly.value == value) { return true; } } return false; } @Override public SSSSGF256Polynomial clone() { return new SSSSGF256Polynomial(this.intValue()); } } /** * Implements a Shamir Secret Sharing Scheme. * * An input stream of bytes (the secret) is split by an encoder * in a number (n) of keys (byte streams) so that k of those keys * can be combined by a decoder to recreate the secret. */ public static final class SSSS { /** * Polynomial interpolator */ private SSSSPolynomialInterpolator interpolator; /** * Pseudo Random Number Generator */ private SecureRandom prng; /** * Create an encoder/decoder using the default * PRNG and the Lagrange polynomial interpolator. */ public SSSS() { interpolator = new SSSSLagrangePolynomialInterpolator(); prng = CryptoHelper.sr; } /** * Create an encoder using the Lagrange polynomial interpolator * and the specified SecureRandom implementation. * * @param rand The SecureRandom instance to use. */ public SSSS(SecureRandom rand) { interpolator = new SSSSLagrangePolynomialInterpolator(); prng = rand; } /** * Create an encoder using the specified SecureRandom implementation * and the specified SSSSPolynomialInterpolator. * * @param rand The SecureRandom instance to use. * @param pi The SSSSPolynomialInterpolator instance to use */ public SSSS(SecureRandom rand, SSSSPolynomialInterpolator pi) { interpolator = pi; prng = rand; } /** * Given k keys, recreate the secret. * If the keys are the wrong ones, the produced data will be random. * To further detect that situation, the secret should contain a * control mechanism to validate the decoded data. * * The decode method operates on streams so input and output can * be of any size. * * @param result The OutputStream to which the reconstructed secret will be written * @param keys Key InputStreams * @throws IOException in case of I/O errors * @throws SSSSDuplicateAbscissaException If the keys cover identical points, probably because they're the wrong ones */ public void decode(OutputStream result, InputStream[] keys) throws IOException, SSSSDuplicateAbscissaException { SSSSXY[] pGroup; do { pGroup = readPoints(keys); if (pGroup != null) { result.write(decodeByte(pGroup)); } } while (pGroup != null); } private int decodeByte(SSSSXY[] points) throws IOException, SSSSDuplicateAbscissaException { SSSSPolynomial fit = interpolator.interpolate(points); // // Decoded byte is P(0) where P is the polynomial which interpolates all points. // SSSSGF256Polynomial result = (SSSSGF256Polynomial) fit.f(new SSSSGF256Polynomial(0)); return result.intValue(); } /** * Read one point from each key * * @param keys Key InputStreams * @return An array of SSSSXY instances or null if EOF is reached on one key * @throws IOException */ private SSSSXY[] readPoints(InputStream[] keys) throws IOException { SSSSXY[] result = new SSSSXY[keys.length]; int xVal, yVal; // // Read one valid x/y pair per key // for (int i = 0; i < result.length; i++) { // // Read one x/y pair, skipping pairs whose x coordinate is 0 // do { xVal = keys[i].read(); if (xVal < 0) { return null; } yVal = keys[i].read(); if (yVal < 0) { return null; } } while (xVal == 0); // // X and Y coordinates are GF256 polynomials // result[i] = new SSSSXY(new SSSSGF256Polynomial(xVal), new SSSSGF256Polynomial(yVal)); } return result; } /** * Given a secret, write out the keys representing that secret. * * @param input The InputStream containing the secret * @param keys OutputStreams for each of the keys. * * @throws IOException If there's an I/O error reading or writing */ public void encode(InputStream input, OutputStream[] keys, int keysNeeded) throws IOException { // // If n < 2 or n > 255 or k < 2 we cannot split, ditto if k > n // if (keys.length < 2 || keys.length > 255 || keysNeeded < 2 || keysNeeded > keys.length) { throw new RuntimeException( "Need to have at least 2 keys and at most 255 and more keys than number of needed keys."); } int v; do { // // Read input byte // v = input.read(); if (v >= 0) { encodeByte(v, keys, keysNeeded); } } while (v >= 0); } /** * Encode a byte * * @param byteVal Byte value to encode * @param keys OutputStreams for the keys * @param keysNeeded Number of keys needed to reconstruct the secret * @throws IOException if an I/O error occurs */ private void encodeByte(int byteVal, OutputStream[] keys, int keysNeeded) throws IOException { // // Array of boolean to keep track of x values already chosen // boolean[] picked = new boolean[256]; // // Select a random polynomial whose value at 0 is the byte value to encode // The degree of the polynomial is the number of keys needed to reconstruct the secret minus one // (Because N points determine uniquely a polynomial of degree N-1, i.e. two points a line, three a parabola...) // SSSSPolynomial rPoly = selectRandomPolynomial(keysNeeded - 1, new SSSSGF256Polynomial(byteVal)); // // Pick a distinct x value per key. // If 0 is chosen as x value, generate a random byte as the associated y coordinate // and pick another x value. // 0 cannot be chosen as P(0) is the encoded value, but we cannot ignore 0 otherwise the // keys would fail a randomness test (since they would not contain enough 0s) // for (int i = 0; i < keys.length; i++) { int xPick; do { // // Pick a random x value // xPick = getRandomByte(); // // If we picked 0, write it out with a random y value to // pass randomness tests // if (xPick == 0) { keys[i].write(xPick); keys[i].write(getRandomByte()); } // Do so while we picked 0 or an already picked x value } while ((xPick == 0) || (picked[xPick] == true)); // Marked the current x value as picked picked[xPick] = true; // Generate x/y SSSSGF256Polynomial xVal = new SSSSGF256Polynomial(xPick); SSSSGF256Polynomial yVal = (SSSSGF256Polynomial) rPoly.f(xVal); // Write x/y pair keys[i].write(xVal.intValue()); keys[i].write(yVal.intValue()); } } /** * Select a random polynomial with coefficients in GF256 whose degree and * value at the origin are fixed. * * @param degree Degree of the polynomial to generate * @param c0 Value of P(0) * @return */ private SSSSPolynomial selectRandomPolynomial(int degree, SSSSGF256Polynomial c0) { SSSSNumber[] coeff = new SSSSNumber[degree + 1]; coeff[0] = c0; for (int i = 1; i < degree; i++) { coeff[i] = new SSSSGF256Polynomial(getRandomByte()); } int cDegree; // // Make sure coefficient for 'degree' is non 0 // do { cDegree = getRandomByte(); } while (cDegree == 0); coeff[degree] = new SSSSGF256Polynomial(cDegree); return new SSSSPolynomial(coeff); } /** * Generate a single byte using the provided PRNG * * @return A random byte */ private int getRandomByte() { byte[] v = new byte[1]; prng.nextBytes(v); return (int) (v[0] & 0xff); } } /** * Split 'data' in N parts, K of which are needed to recover 'data'. * * K should be > N/2 so there are no two independent sets of secrets * in the wild which could be used to recover the initial data. * * @param data * @param n * @param k * @return */ public static List<byte[]> SSSSSplit(byte[] data, int n, int k) { // // If n < 2 or k < 2 we cannot split, ditto if k > n // if (n < 2 || n > 255 || k < 2 || k > n) { return null; } List<byte[]> secrets = new ArrayList<byte[]>(); SSSS ss = new SSSS(); ByteArrayInputStream bais = new ByteArrayInputStream(data); ByteArrayOutputStream[] baos = new ByteArrayOutputStream[n]; for (int i = 0; i < n; i++) { baos[i] = new ByteArrayOutputStream(); } try { ss.encode(bais, baos, k); } catch (IOException ioe) { return null; } // // Retrieve secrets from ByteArrayOutputStream instances // for (int i = 0; i < n; i++) { secrets.add(baos[i].toByteArray()); } return secrets; } /** * Recover data from a list of secrets which is a sublist of a list generated by 'split' * * @param secrets * @return */ public static byte[] SSSSRecover(Collection<byte[]> secrets) { SSSS ss = new SSSS(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayInputStream[] bais = new ByteArrayInputStream[secrets.size()]; int i = 0; for (byte[] secret : secrets) { bais[i] = new ByteArrayInputStream(secret); i++; } try { ss.decode(baos, bais); } catch (SSSSDuplicateAbscissaException dpe) { return null; } catch (IOException ioe) { return null; } return baos.toByteArray(); } // // PGP Related code // public static List<PGPPublicKey> PGPPublicKeysFromKeyRing(String keyring) throws IOException { PGPObjectFactory factory = new PGPObjectFactory( PGPUtil.getDecoderStream(new ByteArrayInputStream(keyring.getBytes("UTF-8")))); List<PGPPublicKey> pubkeys = new ArrayList<PGPPublicKey>(); do { Object o = factory.nextObject(); if (null == o) { break; } if (o instanceof PGPKeyRing) { PGPKeyRing ring = (PGPKeyRing) o; Iterator<PGPPublicKey> iter = ring.getPublicKeys(); while (iter.hasNext()) { PGPPublicKey key = iter.next(); pubkeys.add(key); } } } while (true); return pubkeys; } public static byte[] encryptPGP(byte[] data, PGPPublicKey key, boolean armored, String name, int compressionAlgorithm, int encAlgorithm) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream out = armored ? new ArmoredOutputStream(baos) : baos; BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(encAlgorithm); dataEncryptor.setWithIntegrityPacket(true); dataEncryptor.setSecureRandom(CryptoHelper.getSecureRandom()); PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor); encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(key)); try { OutputStream encout = encryptedDataGenerator.open(out, 1024); PGPCompressedDataGenerator pgpcdg = new PGPCompressedDataGenerator(compressionAlgorithm); OutputStream compout = pgpcdg.open(encout); PGPLiteralDataGenerator pgpldg = new PGPLiteralDataGenerator(false); OutputStream ldout = pgpldg.open(compout, PGPLiteralData.BINARY, name, data.length, PGPLiteralData.NOW); ldout.write(data); ldout.close(); compout.close(); encout.close(); out.close(); baos.close(); return baos.toByteArray(); } catch (PGPException pgpe) { throw new IOException(pgpe); } } }