如何RSAvalidation在php中生成的java中的签名

我们使用phpseclib进行公钥签名,android java用于公钥validation。 但它重复失败了。

PHP代码用于生成密钥和通过私钥签名

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代码

 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); } } 

我编译了java代码

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

然后发现以下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.(RSAPublicKeyImpl.java:83) at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:298) at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:201) ... 2 more 

免责声明:我对java一无所知。 我尝试从网上找到的所有代码。

更新:问题最终解决,java代码能够通过Maarten Bodewes的帮助进行validation。 他提供的代码使用了我需要从phpseclib传递PKCS1的一个更改所以我改变了

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

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

PHP代码需要更改才能使用符号而不是手动加密/散列。

 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); 

PKCS#1键几乎与X.509键完全相同。

以下代码段将创建符合Java JCA的公钥。 然后它将尝试执行(默认)OAEP解密。

 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)); } } 

OAEP的SunJCE实现将失败,因为它不接受用于签名validation的公钥:

OAEP不能用于签名或validation签名

现在,这必须是我在加密API中遇到的最明确和信息量最大的例外之一。 您也可以使用Bouncy Castle提供程序,这个将“解密”哈希值。 然而,这不是应该如何使用OAEP,您应该使用PSS来validation签名。

您应该使用PHP RSA sign方法,使用setHash设置SHA-256。

尽管Martin的答案有效,但还有另一种方法可以摆脱InvalidKeySpecExceptionexception。

在您的原始代码中,pkey是PKCS1格式的RSA私钥。 它必须是PKCS8格式的私钥才能使用X509EncodedKeySpec (对应于X509证书的SubjectPublicKeyInfo)。 它还需要被base64解码。

因此,在您的PHP代码中,您不会执行$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1) – 您需要执行$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS8)

我自己将PKCS1密钥转换为PKCS8并得到了:

 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); 

当然,您需要删除现有的pkey和encodedPublicKey分配。

此外,您可以在PHP代码中return $d而不是return array("publickey"=>$d['publickey'], "privatekey"=>$d['privatekey'])