EMV RSA Recovery Function in c#

32 views Asked by At

I'm implementing the EMV issuer public key recovery in .net8.0 but cannot get this unit test to pass

// from 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];

    // validate 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));
    
    // test recovery algo
    var recovered = Crypto.PerformRSA(data, exp, modulus);
    Assert.Equal(0x6A, recovered[0]);
    Assert.Equal(0xBC, recovered.Last());

Here is the implementation

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

    if (expBytes[0] >= (byte)0x80)
    {
        // Prepend 0x00 to exponent
        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)
    {
        // Prepend 0x00 to modulus
        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)
    {
        // Prepend 0x00 to signed data to avoid that the most significant bit is interpreted as the "signed" bit
        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)
    {
        // Remove 0x00 from beginning of array
        byte[] tmp = new byte[inBytesLength];
        Array.Copy(result, 1, tmp, 0, inBytesLength);
        result = tmp;
    }

    return result;
}

I can't figure out why this is failing. The data is from the test in javaemvreader IssuerPublicKeyCertificate.java but my own card data also doesn't pass. Please any help will be appreciated.

1

There are 1 answers

0
Tolu Ogunremi On

Finally worked it out. the issue had to do with little endian vs big endian. Below is the working RSA Recovery Function in c#:

public static string RecoverSigned(string signedHex, string expHex, string modHex)
{
    var exp = System.Numerics.BigInteger.Parse("00" + expHex, System.Globalization.NumberStyles.HexNumber);
    var mod = System.Numerics.BigInteger.Parse("00" + modHex, System.Globalization.NumberStyles.HexNumber);
    var data = System.Numerics.BigInteger.Parse("00" + signedHex, System.Globalization.NumberStyles.HexNumber);

    var result = System.Numerics.BigInteger.ModPow(data, exp, mod);

    return result.ToString("X");
}