You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:52:06