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

.NET 8中EMV发卡行公钥恢复RSA函数单元测试失败求助

EMV发卡行公钥恢复功能单元测试失败排查

问题背景

在.NET 8中实现EMV发卡行公钥恢复功能,CA验证步骤已通过,但自定义PerformRSA函数执行恢复算法后,结果不满足断言(首字节应为0x6A,末字节应为0xBC),自有卡片数据测试同样失败。测试数据取自javaemvreader的IssuerPublicKeyCertificate.java测试用例。

测试代码

// 来自javaemvreader测试用例
var modulus = Convert.FromHexString("be9e1fa5e9a803852999c4ab432db28600dcd9dab76dfaaa47355a0fe37b1508ac6bf38860d3c6c2e5b12a3caaf2a7005a7241ebaa7771112c74cf9a0634652fbca0e5980c54a64761ea101a114e0f0b5572add57d010b7c9c887e104ca4ee1272da66d997b9a90b5a6d624ab6c57e73c8f919000eb5f684898ef8c3dbefb330c62660bed88ea78e909aff05f6da627b");
var data = Convert.FromHexString("8b3901f6253048a8b2cb08974a4245d90e1f0c4a2a69bca469615a71db21ee7b3aa94200cfaedcd6f0a7d9ad0bf79213b6a418d7a49d234e5c9715c9140d87940f2e04d6971f4a204c927a455d4f8fc0d6402a79a1ce05aa3a526867329853f5ac2feb3c6f59ff6c453a7245e39d73451461725795ed73097099963b82ebf7203c1f78a529140c182dbbe6b42ae00c02");
var hash = Convert.FromHexString("ee1511cec71020a9b90443b37b1d5f6e703030f6");
var rid = Convert.FromHexString("A000000003");
var index = (byte)149;
byte[] exp = [0x03];

// CA验证步骤(已通过)
var ms = new MemoryStream();
ms.Write(rid);
ms.WriteByte(index);
ms.Write(modulus);
ms.Write(exp);
var toHash = ms.ToArray();
var hashed = Crypto.SHA1(toHash);
Assert.True(Enumerable.SequenceEqual(hash, hashed));

// 公钥恢复算法测试(失败)
var recovered = Crypto.PerformRSA(data, exp, modulus);
Assert.Equal(0x6A, recovered[0]);
Assert.Equal(0xBC, recovered.Last());

自定义PerformRSA实现代码

public static byte[] PerformRSA(byte[] dataBytes, byte[] expBytes, byte[] modBytes)
{
    int inBytesLength = dataBytes.Length;

    if (expBytes[0] >= (byte)0x80)
    {
        // 给指数前置0x00
        byte[] tmp = new byte[expBytes.Length + 1];
        tmp[0] = (byte)0x00;
        Array.Copy(expBytes, 0, tmp, 1, expBytes.Length);
        expBytes = tmp;
    }

    if (modBytes[0] >= (byte)0x80)
    {
        // 给模数前置0x00
        byte[] tmp = new byte[modBytes.Length + 1];
        tmp[0] = (byte)0x00;
        Array.Copy(modBytes, 0, tmp, 1, modBytes.Length);
        modBytes = tmp;
    }

    if (dataBytes[0] >= (byte)0x80)
    {
        // 给签名数据前置0x00,避免最高位被解释为符号位
        byte[] tmp = new byte[dataBytes.Length + 1];
        tmp[0] = (byte)0x00;
        Array.Copy(dataBytes, 0, tmp, 1, dataBytes.Length);
        dataBytes = tmp;
    }

    System.Numerics.BigInteger exp = new(expBytes);
    System.Numerics.BigInteger mod = new(modBytes);
    System.Numerics.BigInteger data = new(dataBytes);

    byte[] result = System.Numerics.BigInteger.ModPow(data, exp, mod).ToByteArray();

    if (result.Length == (inBytesLength + 1) && result[0] == (byte)0x00)
    {
        // 移除结果开头的0x00
        byte[] tmp = new byte[inBytesLength];
        Array.Copy(result, 1, tmp, 0, inBytesLength);
        result = tmp;
    }

    return result;
}

问题排查与修复

核心问题在于字节序不匹配和结果格式处理缺失:

  1. EMV规范中所有RSA相关数据(模数、指数、签名)采用大端(Big-Endian)存储,但.NET BigInteger默认解析小端(Little-Endian)字节数组,导致数值解析错误。
  2. 现有代码未处理计算结果的字节序反转,也没有严格对齐EMV要求的公钥长度。

修复后的PerformRSA代码

public static byte[] PerformRSA(byte[] dataBytes, byte[] expBytes, byte[] modBytes)
{
    // 大端字节数组转BigInteger适配方法
    static BigInteger BigEndianToBigInteger(byte[] bytes)
    {
        byte[] reversed = (byte[])bytes.Clone();
        Array.Reverse(reversed);
        // 若最高位为1,追加0x00避免解析为负数
        if ((bytes[0] & 0x80) != 0)
        {
            Array.Resize(ref reversed, reversed.Length + 1);
            reversed[reversed.Length - 1] = 0x00;
        }
        return new BigInteger(reversed);
    }

    // 解析大端格式的参数
    BigInteger exp = BigEndianToBigInteger(expBytes);
    BigInteger mod = BigEndianToBigInteger(modBytes);
    BigInteger data = BigEndianToBigInteger(dataBytes);

    // 执行RSA模幂运算
    BigInteger resultNum = BigInteger.ModPow(data, exp, mod);

    // 将计算结果转回大端字节数组
    byte[] resultBytes = resultNum.ToByteArray();
    // 移除末尾的符号位0x00
    if (resultBytes.Length > 0 && resultBytes[^1] == 0x00)
    {
        Array.Resize(ref resultBytes, resultBytes.Length - 1);
    }
    // 反转得到大端格式
    Array.Reverse(resultBytes);

    // 对齐到模数长度(EMV公钥恢复后长度需匹配模数)
    int expectedLength = modBytes.Length;
    if (resultBytes.Length < expectedLength)
    {
        byte[] padded = new byte[expectedLength];
        Array.Copy(resultBytes, 0, padded, expectedLength - resultBytes.Length, resultBytes.Length);
        resultBytes = padded;
    }
    else if (resultBytes.Length > expectedLength)
    {
        byte[] truncated = new byte[expectedLength];
        Array.Copy(resultBytes, resultBytes.Length - expectedLength, truncated, 0, expectedLength);
        resultBytes = truncated;
    }

    return resultBytes;
}

修复说明

  • 字节序转换:通过反转字节数组,将EMV大端数据适配为.NET BigInteger要求的小端格式,同时处理符号位避免负数解析。
  • 结果格式还原:计算完成后反转字节数组,恢复为EMV要求的大端格式,并通过补0或截断确保长度与模数一致,符合EMV公钥恢复的格式规范。

内容的提问来源于stack exchange,提问作者Tolu Ogunremi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 10:23:12