Skip to content

Commit 27ea49d

Browse files
committed
Fix endianness issue and key generation in ECDH
1 parent 4de5dfb commit 27ea49d

File tree

6 files changed

+212
-18
lines changed

6 files changed

+212
-18
lines changed

crypto/src/bcpg/ECDHPublicBCPGKey.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22

33
using Org.BouncyCastle.Asn1;
4+
using Org.BouncyCastle.Math;
45
using Org.BouncyCastle.Math.EC;
56

67
namespace Org.BouncyCastle.Bcpg
@@ -48,6 +49,21 @@ public ECDHPublicBcpgKey(
4849
VerifySymmetricKeyAlgorithm();
4950
}
5051

52+
public ECDHPublicBcpgKey(
53+
DerObjectIdentifier oid,
54+
BigInteger encodedPoint,
55+
HashAlgorithmTag hashAlgorithm,
56+
SymmetricKeyAlgorithmTag symmetricKeyAlgorithm)
57+
: base(oid, encodedPoint)
58+
{
59+
reserved = 1;
60+
hashFunctionId = hashAlgorithm;
61+
symAlgorithmId = symmetricKeyAlgorithm;
62+
63+
VerifyHashAlgorithm();
64+
VerifySymmetricKeyAlgorithm();
65+
}
66+
5167
public virtual byte Reserved
5268
{
5369
get { return reserved; }

crypto/src/openpgp/PgpEncryptedDataGenerator.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public override void AddSessionInfo(
104104

105105
private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
106106
{
107+
AsymmetricKeyParameter akp = pubKey.GetKey();
108+
107109
if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
108110
{
109111
IBufferedCipher c;
@@ -127,7 +129,6 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
127129
throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
128130
}
129131

130-
AsymmetricKeyParameter akp = pubKey.GetKey();
131132
c.Init(true, new ParametersWithRandom(akp, random));
132133
return c.DoFinal(sessionInfo);
133134
}
@@ -136,12 +137,13 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
136137
KeyParameter key;
137138
byte[] encodedPublicKey;
138139

139-
if (ecKey.CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
140+
if (akp is X25519PublicKeyParameters)
140141
{
142+
X25519PublicKeyParameters pub = (X25519PublicKeyParameters)akp;
141143
byte[] privateKey = new byte[X25519.PointSize];
142144
X25519.GeneratePrivateKey(random, privateKey);
143145
byte[] sharedKey = new byte[32];
144-
X25519.CalculateAgreement(privateKey, 0, BigIntegers.AsUnsignedByteArray(ecKey.EncodedPoint), 1, sharedKey, 0);
146+
X25519.CalculateAgreement(privateKey, 0, pub.GetEncoded(), 0, sharedKey, 0);
145147
byte[] publicKey = new byte[X25519.PointSize + 1];
146148
publicKey[0] = 0x40; // compressed point
147149
X25519.GeneratePublicKey(privateKey, 0, publicKey, 1);
@@ -158,7 +160,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
158160
ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
159161
ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
160162

161-
ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
163+
ECPublicKeyParameters pub = (ECPublicKeyParameters)akp;
162164
ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
163165

164166
key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));

crypto/src/openpgp/PgpPublicKey.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Org.BouncyCastle.Crypto.Parameters;
1212
using Org.BouncyCastle.Math;
1313
using Org.BouncyCastle.Math.EC;
14+
using Org.BouncyCastle.Math.EC.Rfc7748;
1415
using Org.BouncyCastle.Math.EC.Rfc8032;
1516
using Org.BouncyCastle.Security;
1617
using Org.BouncyCastle.Utilities;
@@ -196,6 +197,19 @@ public PgpPublicKey(
196197
ecK.Encode(encodedPoint, 1);
197198
bcpgKey = new ECDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, encodedPoint));
198199
}
200+
else if (pubKey is X25519PublicKeyParameters)
201+
{
202+
X25519PublicKeyParameters ecK = (X25519PublicKeyParameters)pubKey;
203+
byte[] encodedPoint = new byte[X25519.PointSize + 1];
204+
encodedPoint[0] = 0x40;
205+
ecK.Encode(encodedPoint, 1);
206+
Array.Reverse(encodedPoint, 1, X25519.PointSize);
207+
bcpgKey = new ECDHPublicBcpgKey(
208+
MiscObjectIdentifiers.Curve25519,
209+
new BigInteger(1, encodedPoint),
210+
HashAlgorithmTag.Sha256,
211+
SymmetricKeyAlgorithmTag.Aes128);
212+
}
199213
else if (pubKey is ElGamalPublicKeyParameters)
200214
{
201215
ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
@@ -510,7 +524,11 @@ public AsymmetricKeyParameter GetKey()
510524
return GetECKey("ECDSA");
511525
case PublicKeyAlgorithmTag.ECDH:
512526
if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
513-
return new X25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 0);
527+
{
528+
byte[] encodedPoint = ((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned();
529+
Array.Reverse(encodedPoint, 1, X25519.PointSize);
530+
return new X25519PublicKeyParameters(encodedPoint, 1);
531+
}
514532
else
515533
return GetECKey("ECDH");
516534
case PublicKeyAlgorithmTag.EdDsa:

crypto/src/openpgp/PgpPublicKeyEncryptedData.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,10 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
217217

218218
if (privKey.Key is X25519PrivateKeyParameters x25519privKeyParams)
219219
{
220-
byte[] sharedKey = new byte[32];
221-
byte[] reversedPrivateKey = new byte[32];
222-
x25519privKeyParams.Encode(reversedPrivateKey, 0);
223-
Array.Reverse((Array)reversedPrivateKey);
224-
X25519.ScalarMult(reversedPrivateKey, 0, pEnc, 1, sharedKey, 0);
220+
byte[] sharedKey = new byte[X25519.PointSize];
221+
byte[] privateKey = new byte[X25519.PointSize];
222+
x25519privKeyParams.Encode(privateKey, 0);
223+
X25519.CalculateAgreement(privateKey, 0, pEnc, 1, sharedKey, 0);
225224
key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, sharedKey));
226225
}
227226
else

crypto/src/openpgp/PgpSecretKey.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,23 @@ internal PgpSecretKey(
5757
secKey = new DsaSecretBcpgKey(dsK.X);
5858
break;
5959
case PublicKeyAlgorithmTag.ECDH:
60+
if (privKey.Key is X25519PrivateKeyParameters)
61+
{
62+
X25519PrivateKeyParameters x25519K = (X25519PrivateKeyParameters)privKey.Key;
63+
byte[] secretKey = new byte[X25519PrivateKeyParameters.KeySize];
64+
x25519K.Encode(secretKey, 0);
65+
Array.Reverse(secretKey);
66+
secKey = new ECSecretBcpgKey(new BigInteger(1, secretKey));
67+
}
68+
else
69+
{
70+
ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
71+
secKey = new ECSecretBcpgKey(ecK.D);
72+
}
73+
break;
6074
case PublicKeyAlgorithmTag.ECDsa:
61-
ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
62-
secKey = new ECSecretBcpgKey(ecK.D);
75+
ECPrivateKeyParameters ecdsaK = (ECPrivateKeyParameters)privKey.Key;
76+
secKey = new ECSecretBcpgKey(ecdsaK.D);
6377
break;
6478
case PublicKeyAlgorithmTag.EdDsa:
6579
Ed25519PrivateKeyParameters edK = (Ed25519PrivateKeyParameters)privKey.Key;
@@ -683,7 +697,11 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
683697
break;
684698
case PublicKeyAlgorithmTag.ECDH:
685699
if (((ECPublicBcpgKey)secret.PublicKeyPacket.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
686-
privateKey = new X25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0);
700+
{
701+
var x = new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned();
702+
Array.Reverse(x);
703+
privateKey = new X25519PrivateKeyParameters(x, 0);
704+
}
687705
else
688706
privateKey = GetECKey("ECDH", bcpgIn);
689707
break;

crypto/test/src/openpgp/test/PgpECDHTest.cs

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System.Text;
55

66
using NUnit.Framework;
7-
7+
using Org.BouncyCastle.Asn1;
8+
using Org.BouncyCastle.Asn1.Gnu;
9+
using Org.BouncyCastle.Asn1.Misc;
810
using Org.BouncyCastle.Asn1.Sec;
911
using Org.BouncyCastle.Asn1.X9;
1012
using Org.BouncyCastle.Crypto;
@@ -13,6 +15,7 @@
1315
using Org.BouncyCastle.Security;
1416
using Org.BouncyCastle.Utilities;
1517
using Org.BouncyCastle.Utilities.Encoders;
18+
using Org.BouncyCastle.Utilities.IO;
1619
using Org.BouncyCastle.Utilities.Test;
1720

1821
namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
@@ -53,6 +56,40 @@ public class PgpECDHTest
5356
"6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" +
5457
"vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg==");
5558

59+
private static readonly byte[] testX25519PubKey =
60+
Base64.Decode(
61+
"mDMEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" +
62+
"L+yb4A20HEVDREggPHRlc3QuZWNkaEBleGFtcGxlLmNvbT6IkAQTFggAOBYhBGoy" +
63+
"UrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B" +
64+
"AheAAAoJEDewWiN8tfzX0ZMA/AhEvrIgu+29eMQeuHOwX1ZY/UssU5TdVROQzGTL" +
65+
"n5cgAP9hIKtt/mZ112HiAHDuWk2JskdtsuopnrEccz4PSEkSDLg4BF/V8F4SCisG" +
66+
"AQQBl1UBBQEBB0DLPhNt/6GHDbb7vZW/iMsbXTZpgJNQiT6QA/4EzgYQLwMBCAeI" +
67+
"eAQYFggAIBYhBGoyUrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsMAAoJEDewWiN8" +
68+
"tfzXU34BAKJJLDee+qJCmUI20sMy/YoKfWmMnH2RBBHmLV8FAJ7vAP0e2wGixEfs" +
69+
"oPqe8fHmvjQGxSByOyQGn7yD+oq9nVzTAA==");
70+
71+
private static readonly byte[] testX25519PrivKey =
72+
Base64.Decode(
73+
"lIYEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" +
74+
"L+yb4A3+BwMCMscozrXr93fOFmtxu/BJjEJrwRl20Jrv9lryfM+SF4UHgVMmJUpJ" +
75+
"1RuTbSnM2KaqHwOgmdrvf2FJnpg1vMafBk1CmopqkRzzrbJ6xQhiPrQcRUNESCA8" +
76+
"dGVzdC5lY2RoQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEajJSvE2/tzdLYmMbN7Ba" +
77+
"I3y1/NcFAl/V8F4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQN7BaI3y1" +
78+
"/NfRkwD8CES+siC77b14xB64c7BfVlj9SyxTlN1VE5DMZMuflyAA/2Egq23+ZnXX" +
79+
"YeIAcO5aTYmyR22y6imesRxzPg9ISRIMnIsEX9XwXhIKKwYBBAGXVQEFAQEHQMs+" +
80+
"E23/oYcNtvu9lb+IyxtdNmmAk1CJPpAD/gTOBhAvAwEIB/4HAwJ7ShSBrUuUAM5r" +
81+
"G4I/gJKo+eBmbNC4NM81eALAF1vcovZPsGsiZ8IgXT64XiC1bpeAoINn6vM4vVbi" +
82+
"LqNKqu6ll3ZgQ4po6vCW9GkhuEMmiHgEGBYIACAWIQRqMlK8Tb+3N0tiYxs3sFoj" +
83+
"fLX81wUCX9XwXgIbDAAKCRA3sFojfLX811N+AQCiSSw3nvqiQplCNtLDMv2KCn1p" +
84+
"jJx9kQQR5i1fBQCe7wD9HtsBosRH7KD6nvHx5r40BsUgcjskBp+8g/qKvZ1c0wA=");
85+
86+
private static readonly byte[] testX25519Message =
87+
Base64.Decode(
88+
"hF4DbDc2fNL0VcUSAQdAqdV0v1D4X9cuGrT7+oQBpMFnw1wdfAcxH9xdO00s2HUw" +
89+
"qB+XkIRETH7yesynLOKajmYftMWZRyTnW2tJUc1w5NFPjPxcbvd2bYmqkY57uAFg" +
90+
"0kcBKhFklH2LRbBNThtQr3jn2YEFbNnhiGfOpoHfCn0oFh5RbXDwm+P3Q3tksvpZ" +
91+
"wEGe2VkxLLe7BWnv/sRINQ2YpuaYshe8hw==");
92+
5693
private void Generate()
5794
{
5895
SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
@@ -107,6 +144,71 @@ private void Generate()
107144
PgpPrivateKey pgpPrivKey = secRing.GetSecretKey().ExtractPrivateKey(passPhrase);
108145
}
109146

147+
private void Generate25519()
148+
{
149+
SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
150+
151+
//
152+
// Generate a master key
153+
//
154+
IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519");
155+
keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random));
156+
157+
AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair();
158+
159+
PgpKeyPair ecdsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow);
160+
161+
//
162+
// Generate an encryption key
163+
//
164+
keyGen = GeneratorUtilities.GetKeyPairGenerator("X25519");
165+
keyGen.Init(new ECKeyGenerationParameters(MiscObjectIdentifiers.Curve25519, random));
166+
167+
AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
168+
169+
PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow);
170+
171+
//
172+
// Generate a key ring
173+
//
174+
char[] passPhrase = "test".ToCharArray();
175+
PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, ecdsaKeyPair,
176+
"[email protected]", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random);
177+
keyRingGen.AddSubKey(ecdhKeyPair);
178+
179+
PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing();
180+
181+
// TODO: add check of KdfParameters
182+
DoBasicKeyRingCheck(pubRing);
183+
184+
PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing();
185+
186+
PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded());
187+
if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded()))
188+
{
189+
Fail("public key ring encoding failed");
190+
}
191+
192+
PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded());
193+
if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded()))
194+
{
195+
Fail("secret key ring encoding failed");
196+
}
197+
198+
// Extract back the ECDH key and verify the encoded values to ensure correct endianness
199+
PgpSecretKey pgpSecretKey = secRing.GetSecretKey(ecdhKeyPair.KeyId);
200+
PgpPrivateKey pgpPrivKey = pgpSecretKey.ExtractPrivateKey(passPhrase);
201+
202+
if (!Arrays.AreEqual(((X25519PrivateKeyParameters)kpEnc.Private).GetEncoded(), ((X25519PrivateKeyParameters)pgpPrivKey.Key).GetEncoded()))
203+
{
204+
Fail("private key round trip failed");
205+
}
206+
if (!Arrays.AreEqual(((X25519PublicKeyParameters)kpEnc.Public).GetEncoded(), ((X25519PublicKeyParameters)pgpSecretKey.PublicKey.GetKey()).GetEncoded()))
207+
{
208+
Fail("private key round trip failed");
209+
}
210+
}
211+
110212
private void TestDecrypt(PgpSecretKeyRing secretKeyRing)
111213
{
112214
PgpObjectFactory pgpF = new PgpObjectFactory(testMessage);
@@ -136,14 +238,14 @@ private void TestDecrypt(PgpSecretKeyRing secretKeyRing)
136238
// }
137239
}
138240

139-
private void EncryptDecryptTest()
241+
private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve)
140242
{
141243
SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
142244

143245
byte[] text = Encoding.ASCII.GetBytes("hello world!");
144246

145-
IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
146-
keyGen.Init(new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random));
247+
IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm);
248+
keyGen.Init(new ECKeyGenerationParameters(curve, random));
147249

148250
AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
149251

@@ -199,6 +301,39 @@ private void EncryptDecryptTest()
199301
}
200302
}
201303

304+
private void GnuPGCrossCheck()
305+
{
306+
PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey);
307+
308+
PgpObjectFactory pgpF = new PgpObjectFactory(testX25519Message);
309+
310+
PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject();
311+
312+
PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0];
313+
314+
PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5);
315+
316+
PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey("test".ToCharArray());
317+
318+
Stream clear = encP.GetDataStream(pgpPrivKey);
319+
320+
pgpF = new PgpObjectFactory(clear);
321+
322+
PgpCompressedData c1 = (PgpCompressedData)pgpF.NextPgpObject();
323+
324+
pgpF = new PgpObjectFactory(c1.GetDataStream());
325+
326+
PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject();
327+
328+
Stream inLd = ld.GetDataStream();
329+
byte[] bytes = Streams.ReadAll(inLd);
330+
331+
if (!Arrays.AreEqual(bytes, Encoding.ASCII.GetBytes("hello world!")))
332+
{
333+
Fail("wrong plain text in decrypted packet");
334+
}
335+
}
336+
202337
public override void PerformTest()
203338
{
204339
//
@@ -215,9 +350,15 @@ public override void PerformTest()
215350

216351
TestDecrypt(secretKeyRing);
217352

218-
EncryptDecryptTest();
353+
EncryptDecryptTest("ECDH", SecObjectIdentifiers.SecP256r1);
354+
355+
EncryptDecryptTest("X25519", MiscObjectIdentifiers.Curve25519);
356+
357+
GnuPGCrossCheck();
219358

220359
Generate();
360+
361+
Generate25519();
221362
}
222363

223364
private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing)

0 commit comments

Comments
 (0)