1

I am trying to use an existing ECDSA key from .NET (CNG provider) so sign data with Bouncy Castle and then to convert between the two signature formats (P1363 and ASN.1). However, I always end up with different signatures. I am not quite sure whether the conversion is incorrect or whether I am using incorrect methods to read the .NET key with Bouncy Castle.

        var ecdsaKey = new ECDsaCng(cngKey);    
        var testData = Encoding.UTF8.GetBytes("test");
        var akPrivate = PrivateKeyFactory.CreateKey(cngKey.Export(CngKeyBlobFormat.Pkcs8PrivateBlob));
        var bcSigner = SignerUtilities.GetSigner("SHA256withECDSA");
        bcSigner.Init(true, akPrivate);
        bcSigner.BlockUpdate(testData, 0, testData.Length);
        var bcSignature = bcSigner.GenerateSignature();
        var asn1Stream = new Asn1InputStream(bcSignature);

        var bcDerSequence = ((DerSequence) asn1Stream.ReadObject());
        var bcR = ((DerInteger) bcDerSequence[0]).PositiveValue;
        var bcS = ((DerInteger) bcDerSequence[1]).PositiveValue;

        var msSignature = ecdsaKey.SignData(testData, HashAlgorithmName.SHA256);

        // P1363 to ASN.1
        var msR = new BigInteger(1, msSignature.Take(msSignature.Length/2).ToArray());
        var msS = new BigInteger(1, msSignature.Skip(msSignature.Length/2).ToArray());
        var msRDer = new DerInteger(msR);
        var msSDer = new DerInteger(msS);
        var msDerSequence = new DerSequence(msRDer, msSDer).GetDerEncoded();

As for some reason bcR and msR are already different, I assume that the issue might be even before the conversion. I've already looked at numerous StackOverflow articles (e.g. Convert signature from P1363 to ASN.1/DER format using Crypto++?, Verifying ECDSA signature with Bouncy Castle in C#, Verifying ECDSA signature with Bouncy Castle in C#), but can't seem to get it right. Any hint would be highly appreciated!

terminal
  • 105
  • 1
  • 8

1 Answers1

1

Unfortunately I have never solved this issue. However, I came up with a different approach for my particular use case. My use case was to read the Pkcs8PrivateBlob .NET key in Kotlin and generate / validate signatures that are compatible with the ones generated in .NET (i.e. signatures generated in .NET should be valid in Kotlin and vice versa). I post my approach here for reference, but note that it is not a solution to the original problem. It is also not a very elegant and most likely not the simplest solution, but I had to get it working somehow.

Newer JDK versions support the P1363 signature format which means that at least I only had to convert the key blob into a format readable by Java, namely PKCS#8. Nonetheless I used BouncyCastle to create the initial keypair in Kotlin.

Converting the private key blob to PKCS#8 in C#

    internal enum KeyBlobMagicNumber : int
    {
        BCRYPT_DSA_PUBLIC_MAGIC = 0x42505344,
        BCRYPT_DSA_PRIVATE_MAGIC = 0x56505344,
        BCRYPT_DSA_PUBLIC_MAGIC_V2 = 0x32425044,
        BCRYPT_DSA_PRIVATE_MAGIC_V2 = 0x32565044,

        BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345,
        BCRYPT_ECDH_PRIVATE_P256_MAGIC = 0x324B4345,
        BCRYPT_ECDH_PUBLIC_P384_MAGIC = 0x334B4345,
        BCRYPT_ECDH_PRIVATE_P384_MAGIC = 0x344B4345,
        BCRYPT_ECDH_PUBLIC_P521_MAGIC = 0x354B4345,
        BCRYPT_ECDH_PRIVATE_P521_MAGIC = 0x364B4345,
        BCRYPT_ECDH_PUBLIC_GENERIC_MAGIC = 0x504B4345,
        BCRYPT_ECDH_PRIVATE_GENERIC_MAGIC = 0x564B4345,

        BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345,
        BCRYPT_ECDSA_PRIVATE_P256_MAGIC = 0x32534345,
        BCRYPT_ECDSA_PUBLIC_P384_MAGIC = 0x33534345,
        BCRYPT_ECDSA_PRIVATE_P384_MAGIC = 0x34534345,
        BCRYPT_ECDSA_PUBLIC_P521_MAGIC = 0x35534345,
        BCRYPT_ECDSA_PRIVATE_P521_MAGIC = 0x36534345,
        BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC = 0x50444345,
        BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC = 0x56444345,

        BCRYPT_RSAPUBLIC_MAGIC = 0x31415352,
        BCRYPT_RSAPRIVATE_MAGIC = 0x32415352,
        BCRYPT_RSAFULLPRIVATE_MAGIC = 0x33415352,
        BCRYPT_KEY_DATA_BLOB_MAGIC = 0x4d42444b,
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct BCRYPT_ECCKEY_BLOB
    {
        internal KeyBlobMagicNumber Magic;
        internal int cbKey;
    }


    // ===== THE CONVERSION PROCESS =====


    byte[] d;

    byte[] blobBytes = // store Pkcs8PrivateBlob here

    // Parse the binary blob into a BCRYPT_ECCKEY_BLOB structure
    // (see https://learn.microsoft.com/en-gb/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_ecckey_blob?redirectedfrom=MSDN)
    unsafe
    {
        fixed (byte* pBlob = blobBytes)
        {
            BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*) pBlob;

            var headerSize = sizeof(BCRYPT_ECCKEY_BLOB);
            var keyData = blobBytes.Skip(headerSize).ToArray();

            d = keyData.Skip(pBcryptBlob->cbKey * 2).ToArray();

            // The values in the blob are big-endian. Check if we need to reverse them to little-endian
            if (BitConverter.IsLittleEndian)
            {
                d = Arrays.Reverse(d);
            }

            // Append 0 to mark the data as unsigned as per the documentation of System.Numerics.BigInteger
            var unsignedByteData = d.Concat(new byte[] {0}).ToArray();
            var dValue = new System.Numerics.BigInteger(unsignedByteData);

            // Create a BouncyCastle BigInteger from the string representation to make sure that we use the
            // same endianess and sign
            var dValueBC = new BigInteger(dValue.ToString());
            var ecDomainParameters = TlsEccUtilities.GetParametersForNamedCurve(NamedCurve.secp384r1);

            // Build a private key from the parsed d value
            var privateKey = new ECPrivateKeyParameters(dValueBC, ecDomainParameters);

            // Export the private key in PKCS#8 format
            var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);

            var result = privateKeyInfo.GetDerEncoded();

        }
    }

Creating a key pair from the PKCS#8 private key in Kotlin with BouncyCastle

class KeyConverter : IKeyConverter {
    init {
        Security.addProvider(BouncyCastleProvider())
    }

    override fun getKeyPair(pkcs8Base64Key: String): KeyPair {
        val decoder = Base64.getDecoder()
        val keyBytes = decoder.decode(pkcs8Base64Key)
        val pkcS8EncodedKeySpec = PKCS8EncodedKeySpec(keyBytes)

        val defaultKeyFactory = KeyFactory.getInstance("EC", "BC")
        val privateKey = defaultKeyFactory.generatePrivate(pkcS8EncodedKeySpec) as ECPrivateKey
        val publicKey = publicKeyFromPrivateKey(privateKey)
        return KeyPair(publicKey, privateKey)
    }

    private fun publicKeyFromPrivateKey(key: ECPrivateKey): PublicKey {
        val bcKeyFactory = KeyFactory.getInstance("EC", "BC")
        val ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp384r1")
        val Q = ecParameterSpec.g.multiply(JCEECPrivateKey(key).d)
        val publicKeySpec = ECPublicKeySpec(Q, ecParameterSpec)
        return bcKeyFactory.generatePublic(publicKeySpec)
    }
}

Using the key pair to create / validate P1363 signatures in Kotlin

class DefaultCryptoProvider(private val keyPair: KeyPair) {

    private val randomSource: SecureRandom = SecureRandom()

    override fun signData(data: ByteArray): ByteArray {
        val signature = Signature.getInstance("SHA256withECDSAinP1363Format")
        signature.initSign(keyPair.private)
        signature.update(data)
        return signature.sign()
    }

    override fun verifyData(data: ByteArray, signatureData: ByteArray): Boolean {
        val signature = Signature.getInstance("SHA256withECDSAinP1363Format")
        signature.initVerify(keyPair.public)
        signature.update(data)
        return signature.verify(signatureData)
    }
}

Hopefully it helps someone with a similar problem at least.

terminal
  • 105
  • 1
  • 8