diff options
author | David Hook <dgh@cryptoworkshop.com> | 2013-11-11 09:15:09 +0400 |
---|---|---|
committer | David Hook <dgh@cryptoworkshop.com> | 2013-11-11 09:15:09 +0400 |
commit | 18e00b832084aa03f6b59a0893a203e4d8659015 (patch) | |
tree | 19b3daa1c763d960f93c846af58d041ac1220153 /pg | |
parent | 738544026e62ed5836a278273f76b5bdf5947e7b (diff) |
added support of OpenPGP ECDH KDF (RFC 6637)
Diffstat (limited to 'pg')
7 files changed, 428 insertions, 110 deletions
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java index 3cb556bc..a935dc33 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java @@ -2,8 +2,8 @@ package org.bouncycastle.bcpg; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.math.BigInteger; +import org.bouncycastle.util.Arrays; import org.bouncycastle.util.io.Streams; /** @@ -62,7 +62,7 @@ public class PublicKeyEncSessionPacket public PublicKeyEncSessionPacket( long keyID, int algorithm, - BigInteger[] data) + byte[][] data) { this.version = 3; this.keyID = keyID; @@ -71,14 +71,7 @@ public class PublicKeyEncSessionPacket for (int i = 0; i != data.length; i++) { - try - { - this.data[i] = new MPInteger(data[i]).getEncoded(); - } - catch (IOException e) - { - throw new IllegalArgumentException("Invalid BigInteger passed to PublicKeyEncSessionPacket"); - } + this.data[i] = Arrays.clone(data[i]); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java index f93bd079..58160a97 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java @@ -1,8 +1,10 @@ package org.bouncycastle.openpgp.operator; +import java.io.IOException; import java.math.BigInteger; import org.bouncycastle.bcpg.ContainedPacket; +import org.bouncycastle.bcpg.MPInteger; import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; @@ -19,56 +21,74 @@ public abstract class PublicKeyKeyEncryptionMethodGenerator switch (pubKey.getAlgorithm()) { - case PGPPublicKey.RSA_ENCRYPT: - case PGPPublicKey.RSA_GENERAL: - break; - case PGPPublicKey.ELGAMAL_ENCRYPT: - case PGPPublicKey.ELGAMAL_GENERAL: - break; - case PGPPublicKey.ECDH: - break; - case PGPPublicKey.DSA: - throw new IllegalArgumentException("Can't use DSA for encryption."); - case PGPPublicKey.ECDSA: - throw new IllegalArgumentException("Can't use ECDSA for encryption."); - default: - throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + break; + case PGPPublicKey.ECDH: + break; + case PGPPublicKey.DSA: + throw new IllegalArgumentException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new IllegalArgumentException("Can't use ECDSA for encryption."); + default: + throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); } } - public BigInteger[] processSessionInfo( + public byte[][] processSessionInfo( byte[] encryptedSessionInfo) throws PGPException { - BigInteger[] data; + byte[][] data; switch (pubKey.getAlgorithm()) { - case PGPPublicKey.RSA_ENCRYPT: - case PGPPublicKey.RSA_GENERAL: - data = new BigInteger[1]; + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + data = new byte[1][]; - data[0] = new BigInteger(1, encryptedSessionInfo); - break; - case PGPPublicKey.ELGAMAL_ENCRYPT: - case PGPPublicKey.ELGAMAL_GENERAL: - byte[] b1 = new byte[encryptedSessionInfo.length / 2]; - byte[] b2 = new byte[encryptedSessionInfo.length / 2]; + data[0] = convertToEncodedMPI(encryptedSessionInfo); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + byte[] b1 = new byte[encryptedSessionInfo.length / 2]; + byte[] b2 = new byte[encryptedSessionInfo.length / 2]; - System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length); - System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length); + System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length); + System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length); - data = new BigInteger[2]; - data[0] = new BigInteger(1, b1); - data[1] = new BigInteger(1, b2); - break; - default: - throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + data = new byte[2][]; + data[0] = convertToEncodedMPI(b1); + data[1] = convertToEncodedMPI(b2); + break; + case PGPPublicKey.ECDH: + data = new byte[1][]; + + data[0] = encryptedSessionInfo; + break; + default: + throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); } return data; } + private byte[] convertToEncodedMPI(byte[] encryptedSessionInfo) + throws PGPException + { + try + { + return new MPInteger(new BigInteger(1, encryptedSessionInfo)).getEncoded(); + } + catch (IOException e) + { + throw new PGPException("Invalid MPI encoding: " + e.getMessage(), e); + } + } + public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) throws PGPException { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java index 9d7a1312..2bb0d96d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java @@ -1,26 +1,41 @@ package org.bouncycastle.openpgp.operator.jcajce; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import javax.crypto.Cipher; - +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.BCPGKey; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.ECSecretBCPGKey; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.jcajce.DefaultJcaJceHelper; import org.bouncycastle.jcajce.NamedJcaJceHelper; import org.bouncycastle.jcajce.ProviderJcaJceHelper; import org.bouncycastle.jce.interfaces.ElGamalKey; +import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.operator.PGPDataDecryptor; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.RFC6637KDFCalculator; public class JcePublicKeyDataDecryptorFactoryBuilder { private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); public JcePublicKeyDataDecryptorFactoryBuilder() { @@ -77,6 +92,10 @@ public class JcePublicKeyDataDecryptorFactoryBuilder public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + throw new PGPException("ECDH requires use of PGPPrivateKey for decryption"); + } return decryptSessionData(keyAlgorithm, privKey, secKeyData); } @@ -88,7 +107,6 @@ public class JcePublicKeyDataDecryptorFactoryBuilder }; } - public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey) { return new PublicKeyDataDecryptorFactory() @@ -96,6 +114,11 @@ public class JcePublicKeyDataDecryptorFactoryBuilder public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData); + } + return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData); } @@ -107,79 +130,108 @@ public class JcePublicKeyDataDecryptorFactoryBuilder }; } + private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData) + throws PGPException + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + byte[] enc = secKeyData[0]; + + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + byte[] pEnc = new byte[pLen]; + + System.arraycopy(enc, 2, pEnc, 0, pLen); + + byte[] keyEnc = new byte[enc[pLen + 2]]; + + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); + + ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap"); + + try + { + c.init(Cipher.UNWRAP_MODE, key); + + Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + + return PGPUtil.unpadSessionData(paddedSessionKey.getEncoded()); + } + catch (GeneralSecurityException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + } + private byte[] decryptSessionData(int keyAlgorithm, PrivateKey privKey, byte[][] secKeyData) throws PGPException { Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); - byte[] plain; - if (keyAlgorithm == PGPPublicKey.ECDH) + try + { + c1.init(Cipher.DECRYPT_MODE, privKey); + } + catch (InvalidKeyException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + + if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT + || keyAlgorithm == PGPPublicKey.RSA_GENERAL) { - plain = null; + byte[] bi = secKeyData[0]; // encoded MPI + + c1.update(bi, 2, bi.length - 2); } else { - try - { - c1.init(Cipher.DECRYPT_MODE, privKey); - } - catch (InvalidKeyException e) - { - throw new PGPException("error setting asymmetric cipher", e); - } + ElGamalKey k = (ElGamalKey)privKey; + int size = (k.getParameters().getP().bitLength() + 7) / 8; + byte[] tmp = new byte[size]; - if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT - || keyAlgorithm == PGPPublicKey.RSA_GENERAL) + byte[] bi = secKeyData[0]; // encoded MPI + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... { - byte[] bi = secKeyData[0]; // encoded MPI - - c1.update(bi, 2, bi.length - 2); + c1.update(bi, 3, bi.length - 3); } else { - ElGamalKey k = (ElGamalKey)privKey; - int size = (k.getParameters().getP().bitLength() + 7) / 8; - byte[] tmp = new byte[size]; - - byte[] bi = secKeyData[0]; // encoded MPI - if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... - { - c1.update(bi, 3, bi.length - 3); - } - else - { - System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); - c1.update(tmp); - } - - bi = secKeyData[1]; // encoded MPI - for (int i = 0; i != tmp.length; i++) - { - tmp[i] = 0; - } - - if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... - { - c1.update(bi, 3, bi.length - 3); - } - else - { - System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); - c1.update(tmp); - } + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); } + bi = secKeyData[1]; // encoded MPI + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = 0; + } - try + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... { - plain = c1.doFinal(); + c1.update(bi, 3, bi.length - 3); } - catch (Exception e) + else { - throw new PGPException("exception decrypting session data", e); + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); } } - return plain; + try + { + return c1.doFinal(); + } + catch (Exception e) + { + throw new PGPException("exception decrypting session data", e); + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java index 97418533..08e227f7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java @@ -1,5 +1,7 @@ package org.bouncycastle.openpgp.operator.jcajce; +import java.io.IOException; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.Key; import java.security.Provider; @@ -8,13 +10,30 @@ import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; - +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.MPInteger; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.crypto.EphemeralKeyPair; +import org.bouncycastle.crypto.KeyEncoder; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.jcajce.DefaultJcaJceHelper; import org.bouncycastle.jcajce.NamedJcaJceHelper; import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.RFC6637KDFCalculator; public class JcePublicKeyKeyEncryptionMethodGenerator extends PublicKeyKeyEncryptionMethodGenerator @@ -22,6 +41,7 @@ public class JcePublicKeyKeyEncryptionMethodGenerator private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); private SecureRandom random; private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); /** * Create a public key encryption method generator with the method to be based on the passed in key. @@ -69,13 +89,61 @@ public class JcePublicKeyKeyEncryptionMethodGenerator { try { - Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm()); + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ECDH) + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + // Generate the ephemeral key pair + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters(ecParams, random)); + + EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder() + { + public byte[] getEncoded(AsymmetricKeyParameter keyParameter) + { + return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false); + } + }); + + EphemeralKeyPair ephKp = kGen.generate(); + + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate(); + + ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint()), "AESWrap"); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); - Key key = keyConverter.getPublicKey(pubKey); + c.init(Cipher.WRAP_MODE, key, random); - c.init(Cipher.ENCRYPT_MODE, key, random); + byte[] paddedSessionData = PGPUtil.padSessionData(sessionInfo); - return c.doFinal(sessionInfo); + byte[] C = c.wrap(new SecretKeySpec(paddedSessionData, PGPUtil.getSymmetricCipherName(sessionInfo[0]))); + byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded(); + + byte[] rv = new byte[VB.length + 1 + C.length]; + + System.arraycopy(VB, 0, rv, 0, VB.length); + rv[VB.length] = (byte)C.length; + System.arraycopy(C, 0, rv, VB.length + 1, C.length); + + return rv; + } + else + { + Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm()); + + Key key = keyConverter.getPublicKey(pubKey); + + c.init(Cipher.ENCRYPT_MODE, key, random); + + return c.doFinal(sessionInfo); + } } catch (IllegalBlockSizeException e) { @@ -89,5 +157,9 @@ public class JcePublicKeyKeyEncryptionMethodGenerator { throw new PGPException("key invalid: " + e.getMessage(), e); } + catch (IOException e) + { + throw new PGPException("unable to encode MPI: " + e.getMessage(), e); + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java index 23f065bf..3a5e90c2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java @@ -13,6 +13,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.jcajce.JcaJceHelper; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; @@ -131,6 +132,27 @@ class OperatorHelper } } + Cipher createKeyWrapper(int encAlgorithm) + throws PGPException + { + try + { + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + return helper.createCipher("AESWrap"); + default: + throw new PGPException("unknown wrap algorithm: " + encAlgorithm); + } + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create cipher: " + e.getMessage(), e); + } + } + private Signature createSignature(String cipherName) throws PGPException { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java index a9fefb7b..edcaf1fb 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java @@ -146,4 +146,40 @@ class PGPUtil return new SecretKeySpec(keyBytes, algName); } + + public static byte[] padSessionData(byte[] sessionInfo) + { + byte[] result = new byte[40]; + + System.arraycopy(sessionInfo, 0, result, 0, sessionInfo.length); + + byte padValue = (byte)(result.length -sessionInfo.length); + + for (int i = sessionInfo.length; i != result.length; i++) + { + result[i] = padValue; + } + + return result; + } + + public static byte[] unpadSessionData(byte[] encoded) + throws PGPException + { + byte padValue = encoded[encoded.length - 1]; + + for (int i = encoded.length - padValue; i != encoded.length; i++) + { + if (encoded[i] != padValue) + { + throw new PGPException("bad padding found in session data"); + } + } + + byte[] taggedKey = new byte[encoded.length - padValue]; + + System.arraycopy(encoded, 0, taggedKey, 0, taggedKey.length); + + return taggedKey; + } } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPECDHTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPECDHTest.java index 9caf5e50..f462e957 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPECDHTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPECDHTest.java @@ -1,7 +1,11 @@ package org.bouncycastle.openpgp.test; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.spec.ECGenParameterSpec; @@ -9,13 +13,21 @@ import java.util.Date; import java.util.Iterator; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.jce.provider.BouncyCastleProvider; 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.PGPKeyRingGenerator; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPUtil; @@ -27,24 +39,28 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProv import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; +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.Arrays; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.test.SimpleTest; +import org.bouncycastle.util.test.UncloseableOutputStream; public class PGPECDHTest extends SimpleTest { byte[] testPubKey = Base64.decode( - "mFIEUb4GwBMIKoZIzj0DAQcCAwS8p3TFaRAx58qCG63W+UNthXBPSJDnVDPTb/sT\n" + - "iXePaAZ/Gh1GKXTq7k6ab/67MMeVFp/EdySumqdWLtvceFKstFBUZXN0IEVDRFNB\n" + - "LUVDREggKEtleSBhbmQgc3Via2V5IGFyZSAyNTYgYml0cyBsb25nKSA8dGVzdC5l\n" + - "Y2RzYS5lY2RoQGV4YW1wbGUuY29tPoh6BBMTCAAiBQJRvgbAAhsDBgsJCAcDAgYV\n" + - "CAIJCgsEFgIDAQIeAQIXgAAKCRD3wDlWjFo9U5O2AQDi89NO6JbaIObC63jMMWsi\n" + - "AaQHrBCPkDZLibgNv73DLgD/faouH4YZJs+cONQBPVnP1baG1NpWR5ppN3JULFcr\n" + - "hcq4VgRRvgbAEggqhkjOPQMBBwIDBLtY8Nmfz0zSEa8C1snTOWN+VcT8pXPwgJRy\n" + - "z6kSP4nPt1xj1lPKj5zwPXKWxMkPO9ocqhKdg2mOh6/rc1ObIoMDAQgHiGEEGBMI\n" + - "AAkFAlG+BsACGwwACgkQ98A5VoxaPVN8cgEAj4dMNMNwRSg2ZBWunqUAHqIedVbS\n" + + "mFIEUb4GwBMIKoZIzj0DAQcCAwS8p3TFaRAx58qCG63W+UNthXBPSJDnVDPTb/sT" + + "iXePaAZ/Gh1GKXTq7k6ab/67MMeVFp/EdySumqdWLtvceFKstFBUZXN0IEVDRFNB" + + "LUVDREggKEtleSBhbmQgc3Via2V5IGFyZSAyNTYgYml0cyBsb25nKSA8dGVzdC5l" + + "Y2RzYS5lY2RoQGV4YW1wbGUuY29tPoh6BBMTCAAiBQJRvgbAAhsDBgsJCAcDAgYV" + + "CAIJCgsEFgIDAQIeAQIXgAAKCRD3wDlWjFo9U5O2AQDi89NO6JbaIObC63jMMWsi" + + "AaQHrBCPkDZLibgNv73DLgD/faouH4YZJs+cONQBPVnP1baG1NpWR5ppN3JULFcr" + + "hcq4VgRRvgbAEggqhkjOPQMBBwIDBLtY8Nmfz0zSEa8C1snTOWN+VcT8pXPwgJRy" + + "z6kSP4nPt1xj1lPKj5zwPXKWxMkPO9ocqhKdg2mOh6/rc1ObIoMDAQgHiGEEGBMI" + + "AAkFAlG+BsACGwwACgkQ98A5VoxaPVN8cgEAj4dMNMNwRSg2ZBWunqUAHqIedVbS" + "dmwmbysD192L3z4A/ReXEa0gtv8OFWjuALD1ovEK8TpDORLUb6IuUb5jUIzY"); byte[] testPrivKey = @@ -58,6 +74,14 @@ public class PGPECDHTest "AheAAAoJEPfAOVaMWj1Tk7YBAOLz007oltog5sLreMwxayIBpAesEI+QNkuJuA2/" + "vcMuAP99qi4fhhkmz5w41AE9Wc/VtobU2lZHmmk3clQsVyuFyg=="); + byte[] testMessage = + Base64.decode( + "hH4Dp5+FdoujIBwSAgMErx4BSvgXY3irwthgxU8zPoAoR+8rhmxdpwbw6ZJAO2GX" + + "azWJ85JNcobHKDeGeUq6wkTFu+g6yG99gIX8J5xJAjBRhyCRcaFgwbdDV4orWTe3" + + "iewiT8qs4BQ23e0c8t+thdKoK4thMsCJy7wSKqY0sJTSVAELroNbCOi2lcO15YmW" + + "6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" + + "vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg=="); + private void generate() throws Exception { @@ -119,6 +143,101 @@ public class PGPECDHTest } } + private void testDecrypt(PGPSecretKeyRing secretKeyRing) + throws Exception + { + PGPObjectFactory pgpF = new PGPObjectFactory(testMessage); + + PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpF.nextObject(); + + PGPPublicKeyEncryptedData encP = (PGPPublicKeyEncryptedData)encList.get(0); + + PGPSecretKey secretKey = secretKeyRing.getSecretKey(); // secretKeyRing.getSecretKey(encP.getKeyID()); +// +// PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey()extractPrivateKey(null); +// +// clear = encP.getDataStream(pgpPrivKey, "BC"); +// +// bOut.reset(); +// +// while ((ch = clear.read()) >= 0) +// { +// bOut.write(ch); +// } +// +// out = bOut.toByteArray(); +// +// if (!areEqual(out, text)) +// { +// fail("wrong plain text in generated packet"); +// } + } + + private void encryptDecryptTest() + throws Exception + { + byte[] text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' }; + + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDH", "BC"); + + keyGen.initialize(new ECGenParameterSpec("P-256")); + + KeyPair kpEnc = keyGen.generateKeyPair(); + + PGPKeyPair ecdhKeyPair = new JcaPGPKeyPair(PGPPublicKey.ECDH, kpEnc, new Date()); + + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + ByteArrayOutputStream ldOut = new ByteArrayOutputStream(); + OutputStream pOut = lData.open(ldOut, PGPLiteralDataGenerator.UTF8, PGPLiteralData.CONSOLE, text.length, new Date()); + + pOut.write(text); + + pOut.close(); + + byte[] data = ldOut.toByteArray(); + + ByteArrayOutputStream cbOut = new ByteArrayOutputStream(); + + PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setProvider("BC").setSecureRandom(new SecureRandom())); + + cPk.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(ecdhKeyPair.getPublicKey()).setProvider("BC")); + + OutputStream cOut = cPk.open(new UncloseableOutputStream(cbOut), data.length); + + cOut.write(data); + + cOut.close(); + + PGPObjectFactory pgpF = new PGPObjectFactory(cbOut.toByteArray()); + + PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpF.nextObject(); + + PGPPublicKeyEncryptedData encP = (PGPPublicKeyEncryptedData)encList.get(0); + + InputStream clear = encP.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(ecdhKeyPair.getPrivateKey())); + + pgpF = new PGPObjectFactory(clear); + + PGPLiteralData ld = (PGPLiteralData)pgpF.nextObject(); + + clear = ld.getInputStream(); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + int ch; + while ((ch = clear.read()) >= 0) + { + bOut.write(ch); + } + + byte[] out = bOut.toByteArray(); + + if (!areEqual(out, text)) + { + fail("wrong plain text in generated packet"); + } + } + public void performTest() throws Exception { @@ -136,6 +255,10 @@ public class PGPECDHTest // PGPSecretKeyRing secretKeyRing = new PGPSecretKeyRing(testPrivKey, new JcaKeyFingerprintCalculator()); + testDecrypt(secretKeyRing); + + encryptDecryptTest(); + generate(); } |