基于C# Bouncy Castle的PBKDF2密钥解封装解密失败问题
问题:Bouncy Castle实现XML加解密时PBKDF2场景密钥解封装失败
我正在用C#结合Bouncy Castle实现基于W3C官方互操作性测试文件的XML加解密模块,针对椭圆曲线证书加密场景。目前前5个ConcatKDF测试用例可以正常运行,但AGRMNT.6在AES密钥解封装时失败,暂未定位原因。
当前核心问题是使用PBKDF2密钥派生方法的AGRMNT.9测试用例:我尝试过自行实现Java-OpenSAML的逻辑但没搞懂,所以改用Bouncy Castle的内置实现。因为XML解析部分还没完成,我写了一个包含所需参数的测试应用,但同样在AES密钥解封装步骤失败。
测试代码如下:
namespace TestApp { using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; using Org.BouncyCastle.Utilities.IO; using System; using System.Globalization; using System.IO; using System.Threading; internal class Program2 { public const String originatorCurveOid = "1.2.840.10045.3.1.7"; private const String originatorPubKeyBase64 = "BN6pd19x4ac3E7+zXw/yoe1riP37GrA0DMdM2Hy4LbeaL0MLFwC3igcudNFtNMJHMyUSIf2odOPoQ9WoJeDVrj8="; private const String recipientPrivKeyPEM = @"-----BEGIN ENCRYPTED PRIVATE KEY----- MIG5MBwGCiqGSIb3DQEMAQMwDgQItpvpA/2E6rQCAgV2BIGYLz2Oq2GXA8kFiQb9 PghnMz+AGBqhUOfFYMtw0YpUvhJ5wIpvFvnDOI2A5NFjSuUuk3KeP1/StsjadnTX ETN5DaxZC+s5wv6hs60w7TGK6HmAtJdUtzSY8+/HN0c4lJYrmRVfUcssNNb26lI7 ZGPCFhm3T4qUCQOikHgblUmHGSBl2Tip0p2fSgVgzve6tCSPfeA5WErcPBU= -----END ENCRYPTED PRIVATE KEY----- "; private const String hmacDigestOid = "1.2.840.113549.2.9"; // HMAC-SHA256 private const String pbkdf2SaltBase64 = "4+sQvLXNJwnT4fXriwV+ag=="; private const Int32 pbkdf2IterationCount = 2000; private const Int32 pbkdf2KeyLength = 32; private const String keyWrapOid = "2.16.840.1.101.3.4.1.45"; //AES-256-KEYWRAP private const String encryptedKeyBase64 = "LZw4qlbO8prnachRXaHglyriNInmu2vh"; private static void Main(String[] args) { CultureInfo oldCI = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); try { ECPublicKeyParameters ecPubKeyParams = GetPublicKeyParams(Convert.FromBase64String(originatorPubKeyBase64), originatorCurveOid); ECPrivateKeyParameters ecPrivKeyParams = GetPrivateKeyParams(recipientPrivKeyPEM, "passwd"); Byte[] sharedSecret = CalculateSharedSecret(ecPrivKeyParams, ecPubKeyParams); Byte[] pbkdf2Salt = Convert.FromBase64String(pbkdf2SaltBase64); IDigest digest = DigestUtilities.GetDigest(hmacDigestOid); Pkcs5S2ParametersGenerator pdb = new Pkcs5S2ParametersGenerator(digest); pdb.Init(sharedSecret, pbkdf2Salt, pbkdf2IterationCount); KeyParameter key = (KeyParameter)pdb.GenerateDerivedMacParameters(pbkdf2KeyLength * 8); Byte[] derivedKey = key.GetKey(); Byte[] encryptedKey = Convert.FromBase64String(encryptedKeyBase64); IWrapper engine = new AesWrapEngine(); KeyParameter keyParam = new KeyParameter(derivedKey); engine.Init(false, keyParam); Byte[] result = engine.Unwrap(encryptedKey, 0, encryptedKey.Length); using (MemoryStream ms = new MemoryStream(result)) { Byte first; Int32 i = -1; do { ++i; first = Convert.ToByte(ms.ReadByte()); } while (first == (Byte)0); if (i > 1) { result = new Byte[ms.Length - i]; result[0] = first; ms.Read(result, 1, result.Length - 1); } } Byte[] decryptedKey = result; Console.WriteLine("Decrypted key: {0}", Hex.ToHexString(decryptedKey)); } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.ReadLine(); } private static ECPrivateKeyParameters GetPrivateKeyParams(String privateKeyPEM, String password) { PemReader pemReader = new PemReader(new StringReader(privateKeyPEM), new PasswordFinder(password)); ECPrivateKeyParameters privateKeyParam = pemReader.ReadObject() as ECPrivateKeyParameters; return privateKeyParam; } private class PasswordFinder : IPasswordFinder { private readonly String password; public PasswordFinder(String pwd) { this.password = pwd; } public Char[] GetPassword() { return this.password.ToCharArray(); } } private static ECPublicKeyParameters GetPublicKeyParams(Byte[] publicKeyBytes, String publicKeyCurveOid) { ECDomainParameters domainParams = GetDomainParams(publicKeyCurveOid); Byte[] decodedBytes; using (MemoryStream ms = new MemoryStream(publicKeyBytes)) { Int32 first = ms.ReadByte(); // Decode the public ephemeral key switch (first) { case 0x00: // infinity throw new IOException("Sender's public key invalid."); case 0x02: // compressed case 0x03: // Byte length calculated as in ECPoint.getEncoded(); decodedBytes = new Byte[1 + (domainParams.Curve.FieldSize + 7) / 8]; break; case 0x04: // uncompressed or case 0x06: // hybrid case 0x07: // Byte length calculated as in ECPoint.getEncoded(); decodedBytes = new Byte[1 + 2 * ((domainParams.Curve.FieldSize + 7) / 8)]; break; default: throw new IOException("Sender's public key has invalid point encoding 0x" + publicKeyBytes[0].ToString("X2")); } decodedBytes[0] = (Byte)first; Streams.ReadFully(ms, decodedBytes, 1, decodedBytes.Length - 1); } ECPoint q = domainParams.Curve.DecodePoint(decodedBytes); return new ECPublicKeyParameters(q, domainParams); } internal static ECDomainParameters GetDomainParams(String curveOid) { X9ECParameters ecCurve = ECNamedCurveTable.GetByOid(new DerObjectIdentifier(curveOid)); ECDomainParameters domainParams = new ECDomainParameters(ecCurve); return domainParams; } private static Byte[] CalculateSharedSecret(ECPrivateKeyParameters ecPrivKeyParams, ECPublicKeyParameters ecPubKeyParams) { IBasicAgreement agreement = AgreementUtilities.GetBasicAgreement("ECDH"); agreement.Init(ecPrivKeyParams); BigInteger sharedSecret = agreement.CalculateAgreement(ecPubKeyParams); Byte[] sharedSecretBytes = BigIntegers.AsUnsignedByteArray(sharedSecret); return sharedSecretBytes; } } }
私钥提取自测试用例附带的PFX文件,公钥及其他参数来自加密测试用例XML文件。我想确认:
- 是不是密钥解封装环节存在错误?(相同的解封装代码在ConcatKDF场景可正常运行)
- 还是KDF参数设置有误?
内容的提问来源于stack exchange,提问作者azur3
相关产品推荐
相关产品推荐

