java - 具有公钥的 RSA 签名作为未在 java 中验证的文本

标签 java php cryptography digital-signature bouncycastle

这个问题是在过去的问题 How to RSA verify a signature in java that was generated in php 的延续中产生的.该代码适用于简单文本。但是现在我需要签署和验证格式也有公钥(验证 key 除外)的文本。

text1:text2:exported-public-key

示例:

53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEAxWg6ErfkN3xu8rk9WsdzjL5GpjAucMmOAQNeZcgMBxN+VmU43EnvsDLSxUZD1e/cvfP2t2/dzhtV6N2IvT7hveuo/zm3+bUK6AnAfo6pM1Ho0z4WetoYOrHdOVNMMPaytXiVkNlXyeWRF6rl9JOe94mMYWRJzygntiD44+MXsB6agsvQmB1l8thg/8+QHNOBBU1yC4pLQwwO2cb1+oIl0svESkGpzHk8xJUl5jL6dDnhqp8+01KE7AGHwvufrsw9TfVSAPH73lwo3mBMVXE4sfXBzC0/YwZ/8pz13ToYiN88DoqzcfD3+dtrjmpoMpymAA5FBc5c6xhPRcrn24KaiwIDAQAB 

PHP 代码:

$rsa = new Crypt_RSA();
$keysize=2048;
 $pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
 $privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
 $Kp = $d['publickey'];
 $Ks = $d['privatekey'];

$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];

$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".$Kp;

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

$signature = $rsa->sign($plainText);
$signedHS = base64_encode($signature);

// Verification

$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);

var_dump($status);        

Java代码

import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
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 org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class VerifySig {

    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 = "MIIBCgKCAQEA+8fKYCT4QiFUdsJ7VdF4xCkVmq/Kwc/10Jl3ie6mvn8hEsC3NAtMJu+Od12gyWYsS0zBDiQ8h2pGZ7p4uWqenc01dRRrq+g968zmoCKPUllPUuR6v9o+wYTX/os4hgaQSBg7DQn4g3BEekcvyk6e6zAMvuhHjeqnrinhCMFgJUhFL8zFNoyaH559C0TNbR6BTKzOoikah8cKhu4UOga0tWDC0I2Ifus/sHOwVaOBkDFIzD6jBxDH/QF8FsrLLTocuIb7Y6lVxFPPtgiUJku6b7wKExV0bPJvm6/Xhv1GX1FpMrA0Ylzj5IFviuviwgo534EcZQ/Hx3aIf4oPG8jVTQIDAQAB";
        byte[] dpkey = Base64.decodeBase64(pkey);
        RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
        String plainData = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEArszIunGg3ievJOpgesYQsp3nPGgrW+3VwkivkkktOXUBRzb3G3mZzidEjG6LxNe/rrNe0UczmnSHQoSBxJCHyUnCWNfScBD66CFG4hLo5Z1gxrP8D2M2lCa6ap2PWcsKiWqlu38EinMeBjBvB4aYpF7+FkFy64ObxR4pfVZxnxradkD0HvvMPLMbyeHxeGqYf8orERf9jfuKTdY8V44rxht2D2fg2WhB1+XL0JulsPvgOaSK3RPnwi+RQAJbihCIh5Zznn0KQCs5pIWoT3XKe1DMpQuEmphSOY9ZUg3AwlOrpRV+565x6GCSc615/6nowmqKzE4T7qT5nbH+ctiEHQIDAQAB";
        String data = "iD96rNeR51BF2TUZSaw+QhW8SnsMXE5AdJiDVmJk6LL55jC26PBCnqXrFo2lsQt8aWRsZc0bHFGCcuIbhHA+Duo1/PwrxTqC5BZFL/frqsRSVa+vpvGEnj3xe4iImTEasMicQzzaAG9IWIgkRZ272lUZ8PqdtTuqAsRIwir6fEsfVs5uIErEWM18R4JxlFBc3LDIjFOFemEPSVIEBHwWht1c/CrdTtxPRIiugEb1jdofEBUNcWPZgfvApVx5+0aS9WTl31AY+RMlvp+13P/FQgAMnH9rvBdopRIVsZUNlMf8AOE2afhLPfOgx+41rzCB2wGCrRGELbml466WJ3wYNQ==";
        byte[] ciphertext = Base64.decodeBase64(data);
        System.out.println(new String(plainData.getBytes(), UTF_8));
        verifyBC(publicKey, plainData, ciphertext);
        System.out.flush();
    }

    private static void verifyBC(PublicKey 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");
        Signature sig = Signature.getInstance( "SHA256withRSA");
        sig.initVerify(publicKey);
        sig.update(plainData.getBytes(UTF_8));
        System.out.println(sig.verify(ciphertext));
    }
}

在纯文本中使用公钥时,它没有给出任何错误,只是返回 false。如果在使用公钥删除后尝试,它会工作并返回 true。

PHP 运行良好,并且在所有情况下都验证了签名。

我怀疑 java 是否无法验证将 base 64 文本/公钥作为文本的数据?

更新:我比较了两次的二进制字节,结果显示有细微差别。

第一个案例

  1. PHP -> ��#C:��sQ
  2. JAVA -> ��/#C:����sQ

第二种情况

  1. PHP -> ��]Q0l�O+
  2. JAVA -> �]Q0l�

如果 php base64 与 apache base64 不兼容?

最佳答案

当我运行 PHP 代码时,我注意到 $Kp 变量包含格式错误的键:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAqCJ/2E+YZvXJyabQmi0zZlaXXGbfXHt8KYS27i+PAJKBODmevTrS
w59S5AOy2l7lB4z5mYHuwdT6bm6YYXgE0gnoX/b2L65xdD9XtlenS4Zm15TVTdR5
zde4nBa0QPKfhFvthOmdPr9xDhDb8Rojy/phX+Ftva33ceTXoB+CtLyidMWbQmUh
ZufnI7MwIOPAIzXNJJ85eyUjBdoNMwlAPZo9vYQWeiwYGyP1fjQwEWZgjCH/LJjl
sNR1X9vp5oi8/4omdnFRvKLpkd5R7WMmMfAyAXe7tcfMSXuVAgMWEj9ZG0ELpXbG
S3CK6nvOp2gFF+AjHo9bCrh397jYotE3HQIDAQAB
-----END RSA PUBLIC KEY-----

当我去掉所有额外的格式并将 key 导出为一行 base64 时,它起作用了。

PHP代码:

function extract_key($pkcs1) {
    # strip out -----BEGIN/END RSA PUBLIC KEY-----, line endings, etc
    $temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $pkcs1, 1);
        $temp = preg_replace('#-+[^-]+-+#', '', $temp);
        return str_replace(array("\r", "\n", ' '), '', $temp);
}

$rsa = new Crypt_RSA();
$keysize=2048;
 $pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
 $privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
 $Kp = $d['publickey'];
 $Ks = $d['privatekey'];


$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];

file_put_contents("pub_verify_key.txt",extract_key($Kver));

$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".extract_key($Kp);

file_put_contents("plain.txt",$plainText);

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

$signature = $rsa->sign($plainText);

$signedHS = base64_encode($signature);

file_put_contents("signedkey.txt", $signedHS);

// Verification

$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);

var_dump($status);

Java代码:

import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.File;
import java.io.FileReader;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class VerifySig {

    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 = fromFile("pub_verify_key.txt");
        byte[] dpkey = Base64.decodeBase64(pkey);
        RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
        String plainData = fromFile("plain.txt");
        String data = fromFile("signedkey.txt");
        byte[] ciphertext = Base64.decodeBase64(data);
        System.out.println(new String(plainData.getBytes(), UTF_8));
        verifyBC(publicKey, plainData, ciphertext);
        System.out.flush();
    }

    private static void verifyBC(PublicKey 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");
        Signature sig = Signature.getInstance( "SHA256withRSA");
        sig.initVerify(publicKey);
        sig.update(plainData.getBytes(UTF_8));
        System.out.println(sig.verify(ciphertext));
    }

    private static String fromFile(String filename) {
        StringBuilder builder = new StringBuilder(8000);
        try {
            FileReader reader = new FileReader(new File(filename));
            int c;
            while((c = reader.read()) != -1) {
                builder.append((char)c);
            }
        } catch(IOException ioe) {
            throw new RuntimeException(ioe);
        }
        return builder.toString();
    }
}

关于java - 具有公钥的 RSA 签名作为未在 java 中验证的文本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34610676/

相关文章:

javascript - 如何通过selenium从由 <br> 标签分隔的元素中提取部分文本?

c# - 何时使用非对称 key 与数字证书

java - BlowFish 解密 - nCFB 模式

java - 如何删除 GridLayout (Java) 中 ImageIcons 之间的空间

java - 如何为@JsProperty设置默认值?

java - 用于 Yubico OpenPGP 智能卡的 PGP 数据加密

php - 我想创建类似于facebook的状态和评论系统

php - Phalcon, IIS, 重写

php - headMeta -appendName 或appendHttpEquiv

windows - Vista/Windows 7 中包含 crypt32.dll 吗?