简体   繁体   中英

Signature Verification in Java for RSA SHA-256

EDIT - Updated with full SAML

I'm trying to verify a signature I receive from a SAML Response in Java. The SAML Response is validated by samltool.io, but I am having a difficult time with the Signature class in java. No matter what I try, it's returning false. I have properly canonicalized my XML and I am able to successfully generate the correct MessageDigest with what I have in my content String. Hopefully someone can point out what I'm doing wrong. Here is my code:

public class SigVer {
    public static void main(String[] args) throws Exception {   
        System.out.println("Initialized");
        
        String pubKeyStr = "MIIDqDCCApCgAwIBAgIGAYXQr1PhMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi02MjkxNDMxMjEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTAeFw0yMzAxMjAxOTM2MDlaFw0zMzAxMjAxOTM3MDlaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi02MjkxNDMxMjEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMgSFX7FkFVNzGyL7EjtIRaJRmj7/Z8diMZyAVy0rBqcIB7aL9Hn3gVefJs09XJiQiuhLVihaD4H1c9RZzS8U089wmef8w0Gc0Bca8jLziAqbzDsph9lQUxoUFCDCyGhamQZvnAk7Rk+PT012ICN/K/eB0q/Un0VEeyTyPhBimfpA2TTe3nC4kjDpKOzQPhjHvjusL0e5VMnDR37iv+yO+VKZ8bDI5WbJPeR2jWh+6EJHUIoNH0XK6Fna4ektpD4vvMSNapjKVKvyFnNoVQGpl/Hc7J0Dh7b/B4veUuYvrFEhj6iAISuD5hM70iL+xaKkf9GLObj8Ay6RTx7kR99WECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAMfjuP24tBNbcEURI9hhbMECVHxfXauVQF1H5z7uWyp5+Pqphw1LApG5KmR/RFDXzePuuVZNFZON27r78w0tixgR5Z0xrwB8Lkdt9LJSwUGNv0dxpdDPFvBoGopjcnIs1zi5MgiTwW/mkYOWJhZDo+E4CheVRyp3H32hvfEG8B+vrEH7CZ8csIK2N4hYiCJP+xktHH6yXDP7fVSWu1CdWDddpyhI0B0Gwo6aBBiyDElsNyVHGwJGT7dnsWvc8bIKP2IPFgE7zxKoARqzV/jAcryyySRiDjbS9kAw84svgWxBJefoaPAtJjhr84o7222QoxMaxi/X5V9ns+S76Wo3A4Q==";
        
        byte[] certBytes = pubKeyStr.getBytes(java.nio.charset.StandardCharsets.UTF_8);

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(Base64.getDecoder().decode(certBytes));
        X509Certificate certificate = (X509Certificate)certFactory.generateCertificate(in);

        PublicKey publicKey = certificate.getPublicKey();

        String content = "<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://10.100.1.123:8080/identityiq/external/registration.jsf\" ID=\"id130347804157883831058335572\" IssueInstant=\"2023-01-20T19:39:44.049Z\" Version=\"2.0\"><saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk81ebtt38SLQ3I65d7</saml2:Issuer><saml2p:Status><saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></saml2p:StatusCode></saml2p:Status><saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id1303478041593882583231621\" IssueInstant=\"2023-01-20T19:39:44.049Z\" Version=\"2.0\"><saml2:Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk81ebtt38SLQ3I65d7</saml2:Issuer><saml2:Subject><saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">matt@revnatech.com</saml2:NameID><saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml2:SubjectConfirmationData NotOnOrAfter=\"2023-01-20T19:44:44.049Z\" Recipient=\"http://10.100.1.123:8080/identityiq/external/registration.jsf\"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore=\"2023-01-20T19:34:44.049Z\" NotOnOrAfter=\"2023-01-20T19:44:44.049Z\"><saml2:AudienceRestriction><saml2:Audience>http://10.100.1.123:8080/identityiq/</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant=\"2023-01-20T19:39:44.049Z\" SessionIndex=\"id1674243584047.1963093145\"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement><saml2:AttributeStatement><saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\"><saml2:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">matt@revnatech.com</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>";
        
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(content.getBytes(java.nio.charset.StandardCharsets.UTF_8));
        byte[] digest = md.digest();
        
        System.out.println(new String(Base64.getEncoder().encode(digest)));

        String signature = "laE5splYJDi8uoMbbQ1tq8bWHPotfZW/aoJ1psWQkuzgc6wS0XS71MIFM9fcZ7rFQEGUWw0GXR6cy/nyDkCaEOH7o+VvKaainpKSktVrAbBtomWIkFx65AsDbHdcN0CZXIz57yRzzfFWjks+fYEHoEM48/HkB8brFxiiyJ17GEd96O7DErlCxen4W874/uOOneEZYBxXtcZnqQylJjgen3C7VyeQATK1AnGba6TK7cdClJjBDapmo/VnWBP6ovH8ZbATdd6u8au1xBqXBu/a+DLwFIdmd2TdDWmT0mkzz/MqdfCtP8W5nOk3UU9ZwfHzSBaQrWH3/mdIgqh8V57BMw==";
        
        try {
            Signature sig = Signature.getInstance("SHA256withRSA");
            sig.initVerify(publicKey);
            sig.update(content.getBytes(java.nio.charset.StandardCharsets.UTF_8));
            boolean result = sig.verify(Base64.getDecoder().decode(signature.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
            
            System.out.println("Returning " + result);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

My SAML Signature block is as follows:

<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod>
            <ds:Reference URI="#id130347804157883831058335572">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"></ec:InclusiveNamespaces>
                    </ds:Transform>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
                <ds:DigestValue>
                    1jaT9pnWmUCL0+atOcxHZ2yxhL5XOIhhHaK3Oz0gDWY=
                </ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
            laE5splYJDi8uoMbbQ1tq8bWHPotfZW/aoJ1psWQkuzgc6wS0XS71MIFM9fcZ7rFQEGUWw0GXR6cy/nyDkCaEOH7o+VvKaainpKSktVrAbBtomWIkFx65AsDbHdcN0CZXIz57yRzzfFWjks+fYEHoEM48/HkB8brFxiiyJ17GEd96O7DErlCxen4W874/uOOneEZYBxXtcZnqQylJjgen3C7VyeQATK1AnGba6TK7cdClJjBDapmo/VnWBP6ovH8ZbATdd6u8au1xBqXBu/a+DLwFIdmd2TdDWmT0mkzz/MqdfCtP8W5nOk3UU9ZwfHzSBaQrWH3/mdIgqh8V57BMw==
        </ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>
                    MIIDqDCCApCgAwIBAgIGAYXQr1PhMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi02MjkxNDMxMjEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTAeFw0yMzAxMjAxOTM2MDlaFw0zMzAxMjAxOTM3MDlaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi02MjkxNDMxMjEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMgSFX7FkFVNzGyL7EjtIRaJRmj7/Z8diMZyAVy0rBqcIB7aL9Hn3gVefJs09XJiQiuhLVihaD4H1c9RZzS8U089wmef8w0Gc0Bca8jLziAqbzDsph9lQUxoUFCDCyGhamQZvnAk7Rk+PT012ICN/K/eB0q/Un0VEeyTyPhBimfpA2TTe3nC4kjDpKOzQPhjHvjusL0e5VMnDR37iv+yO+VKZ8bDI5WbJPeR2jWh+6EJHUIoNH0XK6Fna4ektpD4vvMSNapjKVKvyFnNoVQGpl/Hc7J0Dh7b/B4veUuYvrFEhj6iAISuD5hM70iL+xaKkf9GLObj8Ay6RTx7kR99WECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAMfjuP24tBNbcEURI9hhbMECVHxfXauVQF1H5z7uWyp5+Pqphw1LApG5KmR/RFDXzePuuVZNFZON27r78w0tixgR5Z0xrwB8Lkdt9LJSwUGNv0dxpdDPFvBoGopjcnIs1zi5MgiTwW/mkYOWJhZDo+E4CheVRyp3H32hvfEG8B+vrEH7CZ8csIK2N4hYiCJP+xktHH6yXDP7fVSWu1CdWDddpyhI0B0Gwo6aBBiyDElsNyVHGwJGT7dnsWvc8bIKP2IPFgE7zxKoARqzV/jAcryyySRiDjbS9kAw84svgWxBJefoaPAtJjhr84o7222QoxMaxi/X5V9ns+S76Wo3A4Q==
                </ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>

I have tried decrypting the SignatureValue with the PublicKey to retrieve what's in the DigestValue, but the value I retrieved when doing that didn't match up. This is what I tried when attempting that:

Cipher c = Cipher.getInstance("RSA");
c.init(Cipher.DECRYPT_MODE, publicKey);     
System.out.println(new String(Base64.getEncoder().encode(c.doFinal(Base64.getDecoder().decode(signature.getBytes(java.nio.charset.StandardCharsets.UTF_8))))));

Your approach is wrong in two ways.

First, in XMLdsig, which SAML uses, the 'real' signature (here the RSA signature) is not over the data or even the data hash as such. Rather, the hash of the data is placed in Reference.DigestValue, and the publickey signature is computed over the SignedInfo element, which includes that DigestValue, canonicalized. Conversely validation checks both that the signature matches the SignedInfo and that the DigestValue in the (or each) Reference matches the (related) data. See https://www.w3.org/TR/xmldsig-core2/#sec-Processing .

Second, the 'backwards RSA' Cipher in Java -- "encrypting with the privatekey and decrypting with the publickey" -- is not correct RSA signature; this is a mistake made back in the 1990s that has never been corrected, apparently for compatibility. Correct PKCS1v1-style signature, now retronymed RSASSA-PKCS1-v1_5 as referenced in that same spec , is four steps:

  1. hash the data
  2. encode the hash (1) in an ASN.1 DigestInfo
  3. pad (2) with 01 FF... 00
  4. take (3) as an integer and modexp with the private exponent

Verification can be done by repeating 1-3 and comparing to the result of modexp-public which reverses 4 OR reversing 4 and 3 (remove 01...) AND 2 (split apart the ASN.1) before comparing to 1. Java's backwards Cipher only does 3 and 4 on the 'encrypt' side and 4 and 3 on the 'decrypt' side leaving you to do 2, as well as 1 on the SignedInfo per above .

This part is a very common mistake; see my list over at https://crypto.stackexchange.com/questions/87006/why-is-data-signed-with-sha256-rsa-pkcs-and-digest-signed-with-rsa-pkcs-different plus
java certificate verify failed
How to use x509 public key to verify a signed/hashed Alexa request body?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM