Skip to content

Commit 5fba3f4

Browse files
committed
Fix endianness issue and key generation in ECDH
1 parent 9f98e0c commit 5fba3f4

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,13 +4,16 @@
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.Crypto;
1012
using Org.BouncyCastle.Crypto.Parameters;
1113
using Org.BouncyCastle.Security;
1214
using Org.BouncyCastle.Utilities;
1315
using Org.BouncyCastle.Utilities.Encoders;
16+
using Org.BouncyCastle.Utilities.IO;
1417
using Org.BouncyCastle.Utilities.Test;
1518

1619
namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
@@ -51,6 +54,40 @@ public class PgpECDHTest
5154
"6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" +
5255
"vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg==");
5356

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

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

137-
private void EncryptDecryptTest()
239+
private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve)
138240
{
139241
SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
140242

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

143-
IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
144-
keyGen.Init(new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random));
245+
IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm);
246+
keyGen.Init(new ECKeyGenerationParameters(curve, random));
145247

146248
AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
147249

@@ -197,6 +299,39 @@ private void EncryptDecryptTest()
197299
}
198300
}
199301

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

214349
TestDecrypt(secretKeyRing);
215350

216-
EncryptDecryptTest();
351+
EncryptDecryptTest("ECDH", SecObjectIdentifiers.SecP256r1);
352+
353+
EncryptDecryptTest("X25519", MiscObjectIdentifiers.Curve25519);
354+
355+
GnuPGCrossCheck();
217356

218357
Generate();
358+
359+
Generate25519();
219360
}
220361

221362
private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing)

0 commit comments

Comments
 (0)