How to RSA verify a signature in java that was generated in php

天大地大妈咪最大 提交于 2019-12-03 09:14:23

PKCS#1 keys are almost but not completely the same as X.509 keys.

The following snippet will create a Java JCA compliant public key. It will then try and perform the (default) OAEP decryption.

package nl.owlstead.stackoverflow;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class PKCS1PublicKey {

    public static RSAPublicKey fromPKCS1Encoding(byte[] pkcs1EncodedPublicKey) {
        // --- parse public key ---
        org.bouncycastle.asn1.pkcs.RSAPublicKey pkcs1PublicKey;
        try {
            pkcs1PublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey
                    .getInstance(pkcs1EncodedPublicKey);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Could not parse BER PKCS#1 public key structure", e);
        }

        // --- convert to JCE RSAPublicKey
        RSAPublicKeySpec spec = new RSAPublicKeySpec(
                pkcs1PublicKey.getModulus(), pkcs1PublicKey.getPublicExponent());
        KeyFactory rsaKeyFact;
        try {
            rsaKeyFact = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("RSA KeyFactory should be available", e);
        }
        try {
            return (RSAPublicKey) rsaKeyFact.generatePublic(spec);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(
                    "Invalid RSA public key, modulus and/or exponent invalid", e);
        }
    }

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        String pkey = "MIIBCgKCAQEA2tF2g/muNw9xKTVcIkjUMvMhygtIW49yo1PgbwqDQ/w9MSfEARtYYF6Tenfz0twaR/eI14GXmlIffflORe4eaSuMBhwQFOIKU/1+v1BV3RLqGGblvHTVaMVm49AGiqxNnh1LBbcSrC5UhMqlL/HGiku0oYsbjLzwcLc5ac6aBQVD60wWGNm1g26lRQGRbCLqxVfcWKT3AMvEQK3cEx/En7/5Vg1V8xnJraNMrO8UGnaX8LLJFzYJiSCEShh7F+pMHbf4MaBekw7Aaf5hPJtczNsR137R92Be3OP4idI5NLmTV+Pi1DWlxhjEhswKH88SP+gsW31gS7B/ddECUqewQwIDAQAB";
        Decoder decoder = Base64.getDecoder();
        byte[] dpkey = decoder.decode(pkey);
        RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);

        String plainData = "Hi I m here";
        String data = "aP0nuYYA1hE5odsCkR/DcdRbBvO2Z8IOlqXf/bKZJiG8HELIop90Vno1dKC1qyHEAOXy0gtH7GtJamzoBjDZmHPT6eto9EZP/xE7xZ8L05kjp0z2thLqO7on4C6DrG++TK1j+E3T7V0UeU874WIB0AEVzu1XUKFW6aeuU67a/gdn8N2n7N/WXtlyNSVZXg8f4PeUhGvFJrhINZT7BuMMZj1gZs4wMJPAICwfvVeg02RPH0N3Ybf2iVgRuZlmtQXGTyBlCxe9ybdHzuQM6nXghpLNmaOzCypb+yVs3Da7E0b3/fKQ7JqPSquWex2ERZbIMSTC6oCzc1rOF6iKVAd92Q==";
        byte[] ciphertext = decoder.decode(data);

        // this will fail of course if the "signature" was generated using OAEP - use PSS signatures instead (see comments below)
        verifyBC(publicKey, plainData, ciphertext);
        System.out.flush();
        decryptBC(publicKey, plainData, ciphertext);
        System.out.flush();
        decryptSun(publicKey, plainData, ciphertext);
        System.out.flush();
    }

    private static void decryptBC(RSAPublicKey publicKey, String plainData,
            byte[] ciphertext) throws Exception {
        Cipher oaep = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding", "BC");
        // this *should* fail
        oaep.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] plaintext = oaep.doFinal(ciphertext);
        System.out.println(new String(plaintext, UTF_8));
    }

    private static void decryptSun(RSAPublicKey publicKey, String plainData,
            byte[] ciphertext) throws Exception {
        Cipher oaep = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding", "SunJCE");
        // this fails beautifully
        oaep.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] plaintext = oaep.doFinal(ciphertext);
        System.out.println(new String(plaintext, UTF_8));
    }

    private static void verifyBC(RSAPublicKey publicKey, String plainData,
            byte[] ciphertext) throws Exception {
        // what should work (for PKCS#1 v1.5 signatures), requires Bouncy Castle provider
        Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");
        sig.initVerify(publicKey);
        sig.update(plainData.getBytes(UTF_8));
        System.out.println(sig.verify(ciphertext));
    }
}

The SunJCE implementation of OAEP will fail because it will not accept the public key for signature verification:

OAEP cannot be used to sign or verify signatures

Now that has to be one of the most clear and informative exceptions I've met in a cryptography API. You can also use the Bouncy Castle provider and this one will "decrypt" the hash value. That's however not how OAEP should be used, you should be using PSS to verify signatures.

You should be using the PHP RSA sign method instead, using setHash to setup SHA-256.

Although Martin's answer works there's another way to get rid of the InvalidKeySpecException exception.

In your original code pkey is a PKCS1 formatted RSA private key. It needs to be a PKCS8 formatted private key to work with X509EncodedKeySpec (which corresponds to an X509 cert's SubjectPublicKeyInfo). It also needs to be base64 decoded.

So in your PHP code you wouldn't do $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1) - you'd do $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS8).

I converted your PKCS1 key to PKCS8 myself and got this:

String pkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2tF2g/muNw9xKTVcIkjU" +
                 "MvMhygtIW49yo1PgbwqDQ/w9MSfEARtYYF6Tenfz0twaR/eI14GXmlIffflORe4e" +
                 "aSuMBhwQFOIKU/1+v1BV3RLqGGblvHTVaMVm49AGiqxNnh1LBbcSrC5UhMqlL/HG" +
                 "iku0oYsbjLzwcLc5ac6aBQVD60wWGNm1g26lRQGRbCLqxVfcWKT3AMvEQK3cEx/E" +
                 "n7/5Vg1V8xnJraNMrO8UGnaX8LLJFzYJiSCEShh7F+pMHbf4MaBekw7Aaf5hPJtc" +
                 "zNsR137R92Be3OP4idI5NLmTV+Pi1DWlxhjEhswKH88SP+gsW31gS7B/ddECUqew" +
                 "QwIDAQAB";
byte[] encodedPublicKey = Base64.decodeBase64(pkey);

You'd, of course, need to remove your existing pkey and encodedPublicKey assignments.

Also, you could do return $d instead of return array("publickey"=>$d['publickey'], "privatekey"=>$d['privatekey']) in your PHP code..

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!