Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of the method RSACryptoServiceProvider signHash in Java

I'm trying to get the equivalent of the following C# method :

public byte[] SignHash(byte[] btHash, string SN)
{
    string strSignature = string.Empty;
    X509Store x509store = null;
    x509store = new X509Store(StoreLocation.CurrentUser);
    x509store.Open(OpenFlags.ReadOnly);

    foreach (X509Certificate2 x509 in x509store.Certificates)
    {
        if (x509.SerialNumber.ToLower().Contains(SN.ToLower()))
        {
            byte[] btSignature = null;
            using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
            {

                key.FromXmlString(x509.PrivateKey.ToXmlString(true));
                return key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
            }
            break;
        }
    }
    return null;

}

In Java language. Actually, I've come to this :

private static String SignHash(final byte[] btHash, String SerialNumber) throws Exception
{
    KeyStore ks = null;
    ks = KeyStore.getInstance("Windows-MY");
    ks.load(null, null);

    Boolean noValidCertificate = true;

    Enumeration<String> en = ks.aliases();

    ArrayList<String> lstAlias = Collections.list(en);

    lstErreurs.add(lstAlias.size() + " certificate(s) found");

    for (String aliasKey : lstAlias)
    {
        X509Certificate cert = (X509Certificate) ks.getCertificate(aliasKey);

        Certificat = Base64Coder.encodeBase64String(cert.getEncoded());

        Boolean blnCertificateFound = false;

        if (SerialNumber != null && !SerialNumber.equals(""))
        {
            String SerialNumberCert = cert.getSerialNumber().toString(16);

            if (SerialNumber.toLowerCase().contains(SerialNumberCert.toLowerCase())
                    || SerialNumberCert.toLowerCase().contains(SerialNumber.toLowerCase()))
            {
                blnCertificateFound = true;
            }
        }

        if (blnCertificateFound == false)
        {
            continue;
        }

        Provider p = ks.getProvider();

        boolean isHashToSign = false;
        for (String strToSign : input.split(";")) {
            if(strToSign.length() == 44 && General.isBase64(strToSign)) {
                isHashToSign = true;
                break;
            }
        }

        String algorithm = "";
        if(isHashToSign)
        {
            algorithm = "RSA";
        } else {
            algorithm = "SHA256withRSA";
        }
        Signature sig = Signature.getInstance(algorithm, p);

        PrivateKey key = (PrivateKey) ks.getKey(aliasKey, "1234".toCharArray());

        if (key != null)
        {
            noValidCertificate = false;

            sig.initSign(key);

            String[] TabToSign = input.split(";");
            String strResultSignature = "";
            String separator = "";
            for (String strToSign : TabToSign)
            {
                byte[] btToSign = null;
                if(isHashToSign) {
                    btToSign = General.Base64_Decode_To_ByteArray(strToSign.getBytes(Charset.forName("UTF-8")));
                } else {
                    btToSign = strToSign.getBytes(Charset.forName("UTF-8"));
                }

                sig.update(btToSign);

                byte[] res = sig.sign();

                String resB64 = Base64Coder.encodeBase64String(res);

                strResultSignature += separator + resB64;
                separator = ";";
            }

            return strResultSignature;
        }
    }

    return null;
}

But getting the algorithm "RSA" does not work for signature. I finally sign the Hash of a Hash in Java. I would like to sign a SHA256 byte array hash without hashing it again. How can I come to this result ? (for information, I'm using the Windows Certificate Store, so I have to work with Sun MSCAPI provider).

EDIT 1 :

I tried with the algorithm "NONEwithRSA" but the signature result is different from the signature in .NET using SignHash method.

EDIT 2 :

The following thread : Difference between SHA256withRSA and SHA256 then RSA explains it is actually possible to sign a hash, but that method requires BouncyCastle.

I can not operate with BouncyCastle because I need to use sun MSCAPI provider (Windows Certificate Store). I have to find an alternative to BouncyCastle (unless BC allows us to use the Sun MSCAPI provider).

like image 965
Thordax Avatar asked Aug 28 '16 21:08

Thordax


1 Answers

(The answer was completely rewritten. Some less interesting thoughts and snippets can be found in the previous revisions)

A call to SignHash(btHash, CryptoConfig.MapNameToOID("SHA256")) does a PKCS#1 v1.5 signature (RSASSA-PKCS1-v1_5), e.g.:

byte[] btHash = new byte[] { 0x57, 0x91, 0x16, 0xB6, 0x3E, 0x06, 0x58, 0x83, 0x24, 0x8C, 0x07, 0x16, 0xDA, 0x6A, 0x03, 0x4D, 0x23, 0x37, 0x0B, 0x32, 0x1C, 0xA0, 0x80, 0x08, 0x1F, 0x42, 0x03, 0x81, 0x8E, 0x54, 0x3A, 0xC6 };
X509Certificate2 cert = new X509Certificate2("dummy.p12", "1234", X509KeyStorageFlags.Exportable);
using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
{
    key.FromXmlString(cert.PrivateKey.ToXmlString(true));
    byte[] ret = key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
}

Gives a signature:

0A5003E549C4E4310F720076A5A4D785B165C4FE352110F6CA9877EB9F364D0C40B0197110304D6F92E8BD40DFD38DB91F356601CDD2CD34129BC54492C2D7F371D431150288A95C21E47533F01A9FA4977439FF9594C703380BEDF49A47A7B060ECAC26AEB53B8732D93E18FAD3B2D5889B3311C1B0D4F9F6B318169BDEB143D771DEFB56BAFE49B2B59F172757D4273EF369AFCB32490EC954E17599BD66D4E3BDB345B860748DB0C3B5A272ECFA546E65F2D4C87870CC62D91680AB71DB52DE618C006356258A941E8F36A5DCC7A06BA6DACAC3DC35F482B168107B4D7DA6C19A56FEDC247232DD7210CA9DB7273AA9AE6A90A8A4DFEB64BA0FBC830AB922

Which contains a PKCS#1 v1.5 padded DigestInfo and hash (when decrypted using the public key):

0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6

As you have only the hash (and not the data) to be signed you need to use the NONEwithRSA algorithm in java (which should perform a PKCS#1 v1.5 padded signature of the input data without any hashing) and generate the correct input DigestInfo with the hash OID manually. Like that (with the help of Apache Commons Lang)::

byte[] btHash = new byte[] { ....the same.... };
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("dummy.p12"), "1234".toCharArray());
PrivateKey privKey = (PrivateKey)keystore.getKey("Dummy", "1234".toCharArray());
byte[] asn=ArrayUtils.addAll(new byte[] { (byte)0x30, (byte)0x31, (byte)0x30, (byte)0x0d, (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03, (byte)0x04, (byte)0x02, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x04, (byte)0x20}, btHash);
Signature signature = Signature.getInstance("NONEwithRSA");
signature.initSign(privKey);
signature.update(asn);
byte[] ret = signature.sign();

Which gives the same signature as the C# code (using the default SunJCE/SunRsaSign providers).


The SunMSCAPI provider supports the NONEWithRSA algorithm with a restriction. Citing sun.security.mscapi.RSASignature javadoc:

NOTE: NONEwithRSA must be supplied with a pre-computed message digest. Only the following digest algorithms are supported: MD5, SHA-1, SHA-256, SHA-384, SHA-512 and a special-purpose digest algorithm which is a concatenation of SHA-1 and MD5 digests.

Which at first sight might work for this scenario. Unfortunately:

Signature mscapiSignatureNoneWithRSA = Signature.getInstance("NONEwithRSA", "SunMSCAPI");
mscapiSignatureNoneWithRSA.initSign(mscapiPrivKey);
mscapiSignatureNoneWithRSA.update(btHash);
byte[] mscapiSignatureNoneWithRSA_btHash = mscapiSignatureNoneWithRSA.sign();

Gives a different signature:

CE26A9F84A85037856D8F910141CE7F68D6CAAB416E5C2D48ACD9677BBACCB46B41500452A79018A22AB1CA866DD878A76B040A343C1BABCDB683AFA8CE1A6CCCA48E3120521E8A7E4F8B62B453565E6A6DC08273084C0748C337724A84929630DC79E2EB1F45F5EEBA2148EC0CA5178F2A232A2AE8A5D22BB06C508659051CD1F5A36951B60F6322C5AEB5D4461FABE4D84E28766501A1583EC2A5D8553C163EC8DB9E80EF972233516BEC50AAC38E54C8B5E3B3DEAE90F37A1D230CD976ABEEE97A4601461E842F0F548DA7A74645AAB42586044B084E6A9F3EFE409EE12D98EB0B16CDC2C0AB75BF5DC9B52EBB7E3303C53A2B1BDF14000B99112B1871A6A

Which contains only a PKCS#1 v1.5 padded value of the hash (there is no ASN.1 DigestInfo sequence /which is wrong in this case/):

0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6

Trying to sign the DigestInfo from the SunJCE example gives an exception:

java.security.SignatureException: Message digest is too long

(Here and here are the reasons for this behavior.)


An alternative way to generate the signature using RSA private key encryption which gives the same signature as the C# code with SunJCE provider (using the same asn variable as above):

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privKey);
byte[] ret = cipher.doFinal(asn);

Does not work with the SunMSCAPI provider:

cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "SunMSCAPI");
cipher.init(Cipher.ENCRYPT_MODE, mscapiPrivKey);
byte[] ret = cipher.doFinal(asn1);

As it gives:

4A540DFAD44EBDAE89BF5DD52121DA831E7C394E0586DC9EAEF949C466E649979E19DF81A44801EBD80B8946605B4EFCED53011A844A3A4E023136F0CDEAA57EAAB1EA1FA75400B3B2D5FAB3955BEB13A178AC03DED6AACA0571412B74BCE30E772082A368B58E94D8E20D8F2A116BA5B3881824A014281E9F04BD687C087ACF7164CAF7C74274BA356A671ADA2BB4142504DB2883AFEDA563C6E590BC962725D6697402AB24391409F50D7D16B8BF64A1C0224C379EF9C7B8E493BE889A70674C3AEEC524366DBF9DE36FEE01F186FC00DE2F06096C46CC873D37E194CB217FBFCCF450C1F96C804022E25E1589DF67247927AAD59C66294B027DD5EE991D46

Which decrypted using the public key gives a nonsense:

3A9E0F985D1CF2CFDB45F201A68EF0F241ADBAED2D945FD36451CB4BE77D9B30D977004F95E6EDC208805E62870CD19D87C5E7F4E4C1AC2546F7F9C410299C9D203C47C2B547BAA55DA05C44DACB7E07C3F0DB99AE291E48A67EE089F8DA34EB1AABE352A7F94B082CFB167C0FE90761B79FCE238A0F3D0D917CA51220EEA9A43877703FC06CDC1F13A77CA9904E3660F7AD84DE0C34C877B737C20B1A117E60D69E6766054A30B95E6E88CF2C11AEE5CE30F2DD780C0334BE085302E73E0E5BB096893B7155E7A48CA16DD5EA9FC6F63090F7223E7FBAAA133BDFDA7251F412E395F4D8A462569BC5DCC34C2DF7D996BB3278C3F6B0E1EE9E729925937C8BAE

But (more interestingly) contains a valid PKCS#1 v1.5 padded encrypted plaintext when decrypted with the private key:

000211F7946FAD6BDB18830F8DD1238FD7EFCCFF041D55B88FBABDAAA6B06A5E9FD7556EB33678D9954D26E07B2FCE6D7304386DBDFC352C9932E2BA1794A3A0E0F6D78AA656DEB36CC483171A77ABF34408F4BF60661ECA79852B8E39C1A710976208FFBF6BE0DFB566149E6C5838762316F394B70BDF6D494F8C43C42CB6E527292DEF9204712CB24AC82C572BBC0E70A298D5FB050A27B54AFFA1332EEF37A14E65D379968BCE717BEC37C67A180DE943AAF2FE83560D33BC588E11B85D1C3391CCB13E4A80F57166BAC9003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6

Which means that although given a private key for the encryption operation the SunMSCAPI uses the public key part (I did not dig into the implementation details to find the reason for this behavior).

So (AFAIK) the SunMSCAPI provider can not be directly used in your scenario...

(Please note that you will get a different result for each encryption run as the PKCS#1 v1.5 encryption padding contains a random data)


Fortunately there are some alternative choices:

[A] Abuse the SunMSCAPI internal API to perform the signature like that (again with the help of Apache Commons Lang):

// Obtain the handles
long hCryptKey = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptKey");
long hCryptProvider = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptProvider");
// Call the internal native method
Class<?> internalClass = Class.forName("sun.security.mscapi.RSASignature");
Method internalSignHashMethod = internalClass.getDeclaredMethod("signHash", boolean.class, byte[].class, int.class, String.class, long.class, long.class);
internalSignHashMethod.setAccessible(true);
byte[] res = (byte[])internalSignHashMethod.invoke(internalClass, false, btHash, btHash.length, "SHA-256", hCryptProvider, hCryptKey);
ArrayUtils.reverse(res); // Make it big endian
  • Which gives the same result as the C# code.

  • But is strongly dependand on the underlying SunMSCAPI implementation which can change at any moment

[B] Use JNI/JNA and call the winapi functions directly

  • Which is a cleaner approach as it depends on a public API

  • I have found this project, but have not given it a try

Good luck!


Appendix: RSA Private key used in the examples:

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDoZFvkEpdzXwSw
9g6cDxg9n/khCjLIO7E8VQFzu80C0iR0C6K05SHvTFEdssmzZmdCQi092ReSJRPH
yAOQUnlcMuCpi0m62ufZ4yNkZX5sH3fjHkP1FMv5CPtJOIArGFCMS4CufXu2XkXh
dbJuCLPJsUuiRsaoRg0Q6a8QVqWAR1oyVojTNFqzZWTLD46lQQIvINOrIeYvKklU
FUNcmq8PyArwEvxaDeiop4gVyizx7n7v213FjAXMfEG920O4DlnKjObdi1+PhejT
1RUxRUipTmAI2d3JmACpYH6+Il8Ck61wmKQ9IjoTstNeRfKGEkxH9RKP2P4ko5w9
8YfToVDXAgMBAAECggEAI5vNIMNghYMXuu3ZCzyc4ER07gUcBuZun+n+kPdD0JzW
jRmDUuiRLJOrEjvlACI+zD5LpGBxZilcQI57TU/13JTHK/N11rXYNODC+Y07s+GW
gyyOCS2om34u0udfbDsLjJO9If+ER0tmtcdNEeMveUY7aqAhrIMfWWoVMxGzxlXd
0kHWl4blnisjc01xCG4WcXVItyA6F8WZ3kL+BTeR5/3IwM72r9k7fcBkJZPLJZff
oZ2W+whGa9UXAkt6DQ6PlWMvt+AVcu84f8k/4FRRUNbY1OslO7zHbEc1K5qibpjb
6Tlfg2G+bux/1oCJ59bdyRP7FQMmgjLx49H17mwcgQKBgQD1j4HRtwSPnS+INPS4
nVpfEtb+wXGeDLCMAgdesgXYfr5JWJKJCKW65/j2TkJ/xoN8L1z8TeE6o6Q3ZHJ9
QtcM1ifCpNCnjjalbkm9BG4bCPy+5MUIuS5oRtJjwb7mPTxzpq4DIj3G2ooY5F2i
9Nyqde3bEvWn910yqQgI6CjOtwKBgQDyRYkU46gKLhp98gJ0zdm3KNZ/TBE5zsO6
rDnwFPLGxanVNMzkIQX/LXAQOaNK1WD8fmKG+iixDVLyJYtVtuvTQLOHkOOTXw44
QY4BGh+wbS0HrfKd7Qcpt/3HTCKq9eW33+jggyBc+fa+LDIGpdbO16SBCP3Cb7k6
9gtBN5du4QKBgQCKriVO3uGAifESJ3Yd3R/wmZ85+N3FuLzsFSk8XaXXgpzMp2z6
XxvZ1rBPyhrcNqyDMex9wS32A/z2G5BdFaaF5VxHHPWJ61MJUqPqT9ovAoBa/rAY
IR0IXxbqp7y8ItFFL1kPBAOHjlx3emE3arpEup0+IBMEbTsBJV0YSqThOQKBgFRf
syX7QwKIm+FQ71oOdsw7BLjAnR8syy2v3V2nbgWbwVHnWZP5jEUaZfTAngXp2iUV
PusTJCjFIyYBvUzUr7yaw+tqolcou6ML8ZCgsHiZDR2njt9BNUVqNo+6DDjN+nrX
GBtYj2TSCQSiD6oRB4Zxw3DM2NNmZXQLTFAiNDMBAoGBAJOu4+nVB8iUE3JBsrM5
WkwBivqTyo9pusxwEs+GHnkVFsFubFKHda04220JMRVevOuf48DPgvlW6UCTojyr
85dFon9tV0qyi9Ehc0OdXqOjmx0y+wdR6ZqG+x+e6JGiYeReIa4XBrq0cLHlzrNY
8UwL0QLJpuaQZEbqhyOGMNKE
-----END PRIVATE KEY-----
like image 146
vlp Avatar answered Sep 16 '22 13:09

vlp