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

旧巷老猫 提交于 2019-12-04 14:41:56

问题


We are using phpseclib for Public key signing of data and android java is used for Public key verification. But it repeatedtly failed.

PHP Code For generating keys and signing by private key

 include_once("phpseclib/autoload.php");

 function getKeys($keysize=2048){

        $rsa = new Crypt_RSA();
        //$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_OPENSSH);
        //$rsa->setPublicKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
        $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
        $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
        $d = $rsa->createKey($keysize);
        return array("publickey"=>$d['publickey'], "privatekey"=>$d['privatekey']);

    }

    function encryptdata($message, $encryptionKey){

        $rsa = new Crypt_RSA();
        //$rsa->setPublicKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
        $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
        $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

        //$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_OPENSSH);
        $rsa->loadKey($encryptionKey); // public key
        return $rsa->encrypt($message);
    } 

    function decryptdata($message, $decryptionKey){

        $rsa = new Crypt_RSA();
//        $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_OPENSSH);
//        $rsa->setPublicKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
        $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
        $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

        $rsa->loadKey($decryptionKey); // private key
        return $rsa->decrypt($message);

    }    

    $keys = getKeys();
    file_put_contents("key.pub", $keys["publickey"]);
    file_put_contents("key.priv", $keys["privatekey"]);

    $publickey = file_get_contents("key.pub");
    $privatekey = file_get_contents("key.priv");

    //print_r($keys);
    $string = "Hi I m here";
    $hash = hash("sha256", $string);

    $encdata = encryptdata($hash, $privatekey);
    echo $base_encdata  = base64_encode($encdata);

JAVA Code

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import org.apache.commons.codec.binary.Base64;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.lang.String;
class PubCheck {
    public static boolean verify(String message, String signature, PublicKey publicKey) throws SignatureException{
    try {
        Signature sign = Signature.getInstance("SHA1withRSA");
        sign.initVerify(publicKey);
        sign.update(message.getBytes("UTF-8"));
        return sign.verify(Base64.decodeBase64(signature.getBytes("UTF-8")));
    } catch (Exception ex) {
        throw new SignatureException(ex);
    }
    }

    public static void main(String[] args) 
    throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException, SignatureException 
    {
    String plainData = "Hi I m here";
    String pkey = "MIIBCgKCAQEA2tF2g/muNw9xKTVcIkjUMvMhygtIW49yo1PgbwqDQ/w9MSfEARtYYF6Tenfz0twaR/eI14GXmlIffflORe4eaSuMBhwQFOIKU/1+v1BV3RLqGGblvHTVaMVm49AGiqxNnh1LBbcSrC5UhMqlL/HGiku0oYsbjLzwcLc5ac6aBQVD60wWGNm1g26lRQGRbCLqxVfcWKT3AMvEQK3cEx/En7/5Vg1V8xnJraNMrO8UGnaX8LLJFzYJiSCEShh7F+pMHbf4MaBekw7Aaf5hPJtczNsR137R92Be3OP4idI5NLmTV+Pi1DWlxhjEhswKH88SP+gsW31gS7B/ddECUqewQwIDAQAB";

    String data = "aP0nuYYA1hE5odsCkR/DcdRbBvO2Z8IOlqXf/bKZJiG8HELIop90Vno1dKC1qyHEAOXy0gtH7GtJamzoBjDZmHPT6eto9EZP/xE7xZ8L05kjp0z2thLqO7on4C6DrG++TK1j+E3T7V0UeU874WIB0AEVzu1XUKFW6aeuU67a/gdn8N2n7N/WXtlyNSVZXg8f4PeUhGvFJrhINZT7BuMMZj1gZs4wMJPAICwfvVeg02RPH0N3Ybf2iVgRuZlmtQXGTyBlCxe9ybdHzuQM6nXghpLNmaOzCypb+yVs3Da7E0b3/fKQ7JqPSquWex2ERZbIMSTC6oCzc1rOF6iKVAd92Q==";

    byte[] encodedPublicKey = pkey.getBytes( "utf-8" );
    //System.out.println(new String(encodedPublicKey, "UTF-8") + "\n");

    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( encodedPublicKey );
    //PKCS8EncodedKeySpec publicKeySpec = new PKCS8EncodedKeySpec(encodedPublicKey);

    KeyFactory keyFactory = KeyFactory.getInstance( "RSA" );

    PublicKey publicKey = keyFactory.generatePublic( publicKeySpec );

    boolean retvar = verify(plainData, data, publicKey);

    // 3 - verifying content with signature and content :
    /*Signature sig = Signature.getInstance( "SHA256withRSA" );
    sig.initVerify( publicKey );
    sig.update( data.getBytes( ) );
    ret = sig.verify( sign.getBytes( ) );*/

    //byte[] decoded = Base64.decodeBase64(data);
    }
}

I compiled java code by

javac -cp commons-codec-1.10.jar:. PubCheck.java
java -cp commons-codec-1.10.jar:. PubCheck

Then found following exception

Exception in thread "main"   java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
at PubCheck.main(PubCheck.java:67)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:387)
at sun.security.x509.X509Key.decode(X509Key.java:403)
at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:83)
at  sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:298)
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:201)
... 2 more

Disclaimer : I have zero knowledge about java. all the code I try found from net.

UPDATE : Issue finally solved and java code able to verify by help from Maarten Bodewes. Code he provided works with one change I need to pass PKCS1 from phpseclib So I changed

Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");

to

Signature sig = Signature.getInstance( "SHA256withRSA");

PHP Code need changes for using sign instead of manually encrypt/hashing.

function getKeys($keysize=2048){

    $rsa = new Crypt_RSA();
    $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
    $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
    $d = $rsa->createKey($keysize);
    return array("publickey"=>$d['publickey'], "privatekey"=>$d['privatekey']);

}


$string = "Hi I m here";
/*
$keys = getKeys();
file_put_contents("key1.pub", $keys["publickey"]);
file_put_contents("key1.priv", $keys["privatekey"]);
die;*/

$publickey = file_get_contents("key1.pub");
$privatekey = file_get_contents("key1.priv");

$hash = new Crypt_Hash('sha256');
$rsa = new Crypt_RSA();    
$rsa->loadKey($privatekey);
$rsa->setSignatureMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');

$signature = $rsa->sign($string);
echo base64_encode($signature);

回答1:


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.




回答2:


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..



来源:https://stackoverflow.com/questions/34522859/how-to-rsa-verify-a-signature-in-java-that-was-generated-in-php

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