从模数和指数重建 RSA 私钥失败

Rebuild of a RSA Private Key from modulus & exponent fails

我正在尝试从模数和 private/public 指数重建 RSA 密钥对。转换对于 public 密钥是正确的,但在比较编码的私钥时无法转换为私钥。

使用此重建 private/public 密钥对进行加密时,它在 Java 中有效(!),但在 PHP 中使用重建密钥对时,解密部分失败(加密有效), 所以在我看来重建私钥与“原始”私钥不同。

仅供参考:使用“原始”密钥对在 PHP 中一切正常。

所以我的问题是:如何从 (BigInteger) 模数和私有指数中检索“原始”私钥?

编辑:见最后我的最终编辑

我的示例代码显示 public 密钥与重建密钥相等,并且私钥不同:

Rebuilding of a RSA PrivateKey from modulus & exponent
privateKey equals rebuild: false
publicKey equals rebuild: true

代码:

import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;

public class RebuildRSAPrivateKey {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        System.out.println("Rebuilding of a RSA PrivateKey from modulus & exponent");
        // rsa key generation
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        //kpGen.initialize(2048, new SecureRandom());
        kpGen.initialize(2048, new SecureRandom());
        KeyPair keyPair = kpGen.generateKeyPair();
        // private key
        PrivateKey privateKey = keyPair.getPrivate();
        // get modulus & exponent
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
        BigInteger modulus = rsaPrivateKey.getModulus();
        BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
        // rebuild the private key
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
        PrivateKey privateKeyRebuild = keyFactory.generatePrivate(rsaPrivateKeySpec);
        System.out.println("privateKey equals rebuild: " + Arrays.equals(privateKey.getEncoded(), privateKeyRebuild.getEncoded()));
        // public key
        PublicKey publicKey = keyPair.getPublic();
        // get modulus & exponent
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        BigInteger modulusPub = rsaPublicKey.getModulus();
        BigInteger publicExponent = rsaPublicKey.getPublicExponent();
        // rebuild the public key
        KeyFactory keyFactoryPub = KeyFactory.getInstance("RSA");
        RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulusPub, publicExponent);
        PublicKey publicKeyRebuild = keyFactory.generatePublic(rsaPublicKeySpec);
        System.out.println("publicKey equals rebuild: " + Arrays.equals(publicKey.getEncoded(), publicKeyRebuild.getEncoded()));
    }
}

编辑: 以下程序将显示从编码密钥派生的 RSA private/public 密钥对可以恢复,并且 加密和解密在 Java 和 PHP 中工作。密钥是 不安全 RSA 512 位密钥和 Base64 解码。

然后从模数和 private/public 指数导出相同的密钥,并且 en-/decryption 在 Java 中有效,但在 PHP[=58 中无效=].

这就是为什么我想从模数和指数中获取“原始”RSA 密钥的原因,感谢您的热心帮助。

Java 程序的结果:

Rebuilding of a RSA PrivateKey from modulus & exponent v4
privateKey Original Base64: MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==
privateKey Rebuild  Base64: MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==
publicKey           Base64: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==

generate private & public key via modulus and private/public exponent
privateKey Modulus  Base64: MIGzAgEAMA0GCSqGSIb3DQEBAQUABIGeMIGbAgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIBAAJBAJA6X+IDUa5rPlv5qUrIcirKA024ffneXjz+tNUJUCO/Za7I3cthi4JOEVdV5sGIpzPUpPBvrLMl02B3W8ugNsECAQACAQACAQACAQACAQA=
publicKey  Modulus  Base64: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==

en-/decryption with original keys
ciphertext Original   : fvFPRZ5B2GMgv9aXQjyQsxnRHK2wotfXlLV+zGea1E3nsZC6RMn+LQMOe9yvZ8IcaG2F/8wWv2NkNmBX4wuxaw==
decryptedtext Original: this is the message to encrypt

en-/decryption with keys from modulus & exponent
ciphertext Modulus    : o0tB4xQIwQRFDSsWj1WgWHexXnJOp9jeBymFPJvy+xZBvfJay2yR0XZEy+0VwaedxdTf9CoyKVvgCbn2HCohSQ==
decryptedtext Modulus : this is the message to encrypt

PHP 程序的结果:

php version: 7.4.6 openssl version: OpenSSL 1.1.1g 21 Apr 2020
plaintext: this is the message to encrypt
rsa encryption with original keys
priBase64:MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==
pubBase64:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==
ciphertext Base64:WmvVwqf2EHQc0yb6L4pVJ0/23pNW4QsBun3SNvYE8p/sEk+1GQSYxYpbY/mLbSGF2Lb1P5g5er+z7dWxHmodNA==
decryptedtext: this is the message to encrypt

rsa encryption with keys created via modulus & exponents
priBase64:MIGzAgEAMA0GCSqGSIb3DQEBAQUABIGeMIGbAgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIBAAJBAJA6X+IDUa5rPlv5qUrIcirKA024ffneXjz+tNUJUCO/Za7I3cthi4JOEVdV5sGIpzPUpPBvrLMl02B3W8ugNsECAQACAQACAQACAQACAQA=
pubBase64:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==
ciphertext Base64:kqn8aZpvfpPzr3u2NBX/XmnlFweEvOm+Qu4l2wiUSQCjA0hutQ10mbLaO55oCox7GixvMgb3VtoDBJ8hfW1zbQ==
Cannot Decrypt error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error
decryptedtext:

decrypt error: error:0909006C:PEM routines:get_name:no start line

来源Java:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.Base64;

public class RebuildRSAPrivateKey4 {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, IOException {
        System.out.println("Rebuilding of a RSA PrivateKey from modulus & exponent v4");
        // rsa key generation
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        //kpGen.initialize(2048, new SecureRandom());
        kpGen.initialize(512, new SecureRandom()); // don't use 512 bit keys as they are insecure !!
        KeyPair keyPair = kpGen.generateKeyPair();

        // privateKey   Base64: MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==
        // publicKey    Base64: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==
        String privateKeyBase64 = "MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==";
        String publicKeyBase64 = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==";
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyBase64));
        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBase64));
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
        System.out.println("privateKey Original Base64: " + privateKeyBase64);
        System.out.println("privateKey Rebuild  Base64: " + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
        System.out.println("publicKey           Base64: " + publicKeyBase64);
        // get modulus & private exponent via RSAPrivateKey
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
        BigInteger modulus = rsaPrivateKey.getModulus();
        BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
        // rebuild the private key
        RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
        PrivateKey privateKeyModulusExponent = keyFactory.generatePrivate(rsaPrivateKeySpec);
        // public key
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        BigInteger modulusPub = rsaPublicKey.getModulus();
        BigInteger publicExponent = rsaPublicKey.getPublicExponent();
        // rebuild the public key
        RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulusPub, publicExponent);
        PublicKey publicKeyModulusExponent = keyFactory.generatePublic(rsaPublicKeySpec);
        System.out.println("\ngenerate private & public key via modulus and private/public exponent");
        System.out.println("privateKey Modulus  Base64: " + Base64.getEncoder().encodeToString(privateKeyModulusExponent.getEncoded()));
        System.out.println("publicKey  Modulus  Base64: " + Base64.getEncoder().encodeToString(publicKeyModulusExponent.getEncoded()));
        System.out.println("\nen-/decryption with original keys");
        String plaintext = "this is the message to encrypt";
        String ciphertextOriginal = encrypt(publicKey, plaintext);
        String decryptedtextOriginal = decrypt(privateKey, ciphertextOriginal);
        System.out.println("ciphertext Original   : " + ciphertextOriginal);
        System.out.println("decryptedtext Original: " + decryptedtextOriginal);
        System.out.println("\nen-/decryption with keys from modulus & exponent");
        String ciphertextModulus = encrypt(publicKeyModulusExponent, plaintext);
        String decryptedtextModulus = decrypt(privateKeyModulusExponent, ciphertextOriginal);
        System.out.println("ciphertext Modulus    : " + ciphertextModulus);
        System.out.println("decryptedtext Modulus : " + decryptedtextModulus);
    }

    private static String encrypt(PublicKey publicKey, String plaintext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException, BadPaddingException, IllegalBlockSizeException {
        String ciphertext = "";
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] ciphertextByte = cipher.doFinal(plaintext.getBytes("UTF8"));
        ciphertext = Base64.getEncoder().encodeToString(ciphertextByte).replaceAll("\r|\n", "");
        return ciphertext;
    }

    private static String decrypt(PrivateKey privateKey, String ciphertext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] ciphertextByte = Base64.getDecoder().decode(ciphertext);
        byte[] decryptedtextByte = cipher.doFinal(ciphertextByte);
        return new String(decryptedtextByte);
    }
    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }

}

来源PHP:

<?php

function encrypt($publicKeyBase64, $plaintext){
    $pub = base64_decode($publicKeyBase64);
    // public key conversion der to pem
    $pubPem = chunk_split(base64_encode($pub), 64, "\n");
    $pubPem = "-----BEGIN PUBLIC KEY-----\n" . $pubPem . "-----END PUBLIC KEY-----\n";
    $ublicKey = "";
    $publicKey = openssl_get_publickey($pubPem);
    if (!$publicKey) {
        echo "Cannot get public key" . "<br>";
    }
    $ciphertext = "";
    openssl_public_encrypt($plaintext, $ciphertext, $publicKey);
    if (!empty($ciphertext)) {
        openssl_free_key($publicKey);
        //echo "Encryption OK!" . "<br>";
    } else {
        echo "Cannot Encrypt" . "<br>";
    }
    $ciphertextBase64 = base64_encode($ciphertext);
    return $ciphertextBase64;
}

function decrypt($privateKeyBase64, $ciphertext){
    $pri = base64_decode($privateKeyBase64);
    // private key conversion der to pem
    $priPem = chunk_split(base64_encode($pri), 64, "\n");
    $priPem = "-----BEGIN PRIVATE KEY-----\n" . $priPem . "-----END PRIVATE KEY-----\n";
    $privateKey = openssl_get_privatekey($priPem);
    $Crypted = openssl_private_decrypt($ciphertext, $decryptedtext, $privateKey);
    if (!$Crypted) {
        echo 'Cannot Decrypt ' . openssl_error_string() . '<br>';
    } else {
        openssl_free_key($privateKey);
        //echo "decryptedtext: " . $decryptedtext . "<br>";
    }
    return $decryptedtext;
}

echo 'php version: ' . PHP_VERSION . ' openssl version: ' . OPENSSL_VERSION_TEXT . '<br>';
$plaintext = "this is the message to encrypt";
echo "plaintext: " . $plaintext . "<br>";

// RSA 512 keys from Java GenerateKeysSo.java
echo 'rsa encryption with original keys' . '<br>';
$priBase64 = "MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIDAQABAkEAkDpf4gNRrms+W/mpSshyKsoDTbh9+d5ePP601QlQI79lrsjdy2GLgk4RV1XmwYinM9Sk8G+ssyXTYHdby6A2wQIhAPcRtl6tub6PFiIE1jcuIkib/HzAdRYHZx3ZdzRTYDetAiEA4uv43xpGl5N8yG27Kv0DkRoOlr4Ch6oM24hLVw7ClhcCIFgdRAo+MQlqJH2bdf6WAHoez4x6YwepOjhmD2Jk/eK9AiEAtHgI6J5EEB56+gfS+CBa6tZ3Tcl1x6ElMp8Vk/ooJScCIQDUa3LUkcc58yjJYq8ZNQC/86+HIzd5MldTwg5buR1lpw==";
$pubBase64 = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==";
echo 'priBase64:' . $priBase64 . '<br>';
echo 'pubBase64:' . $pubBase64 . '<br>';
$ciphertextBase64 = encrypt($pubBase64, $plaintext);
echo 'ciphertext Base64:' . $ciphertextBase64 . '<br>';
$ciphertext = base64_decode($ciphertextBase64);
$decryptedtext = decrypt($priBase64, $ciphertext);
echo "decryptedtext: " . $decryptedtext . "<br><br>";

// keys created via modulus & exponent
$priBase64 = "MIGzAgEAMA0GCSqGSIb3DQEBAQUABIGeMIGbAgEAAkEA2wFgcni89ijJ/uijQkzCGF4JiUB1+mEJ48u4Lk0vxB7ym3/FCvOEnN2H7FLUzsGvXRhFriLBiSJlg2tOhV5eiwIBAAJBAJA6X+IDUa5rPlv5qUrIcirKA024ffneXjz+tNUJUCO/Za7I3cthi4JOEVdV5sGIpzPUpPBvrLMl02B3W8ugNsECAQACAQACAQACAQACAQA=";
$pubBase64 = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANsBYHJ4vPYoyf7oo0JMwhheCYlAdfphCePLuC5NL8Qe8pt/xQrzhJzdh+xS1M7Br10YRa4iwYkiZYNrToVeXosCAwEAAQ==";
echo 'rsa encryption with keys created via modulus & exponents' . '<br>';
echo 'priBase64:' . $priBase64 . '<br>';
echo 'pubBase64:' . $pubBase64 . '<br>';
$ciphertextBase64 = encrypt($pubBase64, $plaintext);
echo 'ciphertext Base64:' . $ciphertextBase64 . '<br>';
$ciphertext = base64_decode($ciphertextBase64);
$decryptedtext = decrypt($priBase64, $ciphertext);
echo "decryptedtext: " . $decryptedtext . "<br><br>";
echo 'decrypt error: error:0909006C:PEM routines:get_name:no start line' . '<br>';
?>

最终编辑解决方案和结论

如果我们想在 Java 以外的其他系统中使用 RSA 私钥-public 密钥对进行加密(以及签名?),立即保存私钥非常重要。如果我们试图从编码形式(通过 PKCS8EncodedKeySpec)重建私钥,肯定会丢失一些数据。 那些重建私钥将无法工作(此处为PHP/openssl)。

如果我们需要从编码形式 (byte[]) 重建私钥,则需要通过名为“createCrtKey”的方法增强密钥 - 此方法由 James K. Polk 总统编写,所有出处去找他。由于链接有时会过时,我在下面将自己的答案标记为已接受,因为那里记录了 createCrtKey 方法。

感谢@President James K. Polk、@Topaco 和@michalk 指引我走向正确的方向。

这是我的程序的修改版本,其中包含来自@President James K. Polk 的附加代码(参见上面 Topaco 的 link)。即使重建的 CRT 私钥现在比重建的私钥长,它也不匹配原始(编码) 私钥。当我为 PHP RSA encryption/decryption 使用编码的私钥和 public 密钥时,有一个有趣的事实,即原始密钥 运行 成功但重建密钥没有.. .

此版本使用不安全的 512 位密钥长度,仅用于演示(使密钥更短)。

结果:

Rebuilding of a RSA PrivateKey from modulus & exponent
privateKey equals rebuild: false
publicKey equals rebuild: true
privateKey original    encoded: 30820154020100300d06092a864886f70d01010105000482013e3082013a020100024100a45477b9f00f51c8e1d5cb961a485c74ee123aa6da5c5bfd43f62acee9b684a8f140bb7a68996a77d04bdaabc5f259cb38a7bef909f4d85c6a597519a09aec9b0203010001024066ea4fa12f6b28b93a567f0e1e9fbae7b041d261b4d7aaf4ce9f58e8050ebdbd5e2a6261f06de2d72c4fdc6a62465f9cad9e8f5860bb2f8395cd903a214fb441022100e3b260dcced139557591b609470d8f0e518351a97bdbf26a59a41140a68778e9022100b8c1ab98f7b7280bd4b53fa3ed09c11d12aec9873d8a4a05e43152bc0d3346e302201d801ff29bcd19bb8bc6fc29c98de529fabfa3d5ec993b9831d302f5385e36f90220009e0d0fbecc2ae3173bdfd1916a35edfdf0fd95691c3c3116d91f58a786a357022100a810110da3d9d4de34e64029a3535368bb52e7b81055239cb4443d5172aea8e5
privateKey rebuild     encoded: 3081b2020100300d06092a864886f70d010101050004819d30819a020100024100a45477b9f00f51c8e1d5cb961a485c74ee123aa6da5c5bfd43f62acee9b684a8f140bb7a68996a77d04bdaabc5f259cb38a7bef909f4d85c6a597519a09aec9b020100024066ea4fa12f6b28b93a567f0e1e9fbae7b041d261b4d7aaf4ce9f58e8050ebdbd5e2a6261f06de2d72c4fdc6a62465f9cad9e8f5860bb2f8395cd903a214fb441020100020100020100020100020100
privateKey rebuild CRT encoded: 30820153020100300d06092a864886f70d01010105000482013d30820139020100024100a45477b9f00f51c8e1d5cb961a485c74ee123aa6da5c5bfd43f62acee9b684a8f140bb7a68996a77d04bdaabc5f259cb38a7bef909f4d85c6a597519a09aec9b0203010001024066ea4fa12f6b28b93a567f0e1e9fbae7b041d261b4d7aaf4ce9f58e8050ebdbd5e2a6261f06de2d72c4fdc6a62465f9cad9e8f5860bb2f8395cd903a214fb441022100b8c1ab98f7b7280bd4b53fa3ed09c11d12aec9873d8a4a05e43152bc0d3346e3022100e3b260dcced139557591b609470d8f0e518351a97bdbf26a59a41140a68778e90220009e0d0fbecc2ae3173bdfd1916a35edfdf0fd95691c3c3116d91f58a786a35702201d801ff29bcd19bb8bc6fc29c98de529fabfa3d5ec993b9831d302f5385e36f9022030634f5490e1bb4b56a68715d3c80a92c6e8f7c9f3e79f125a9969e6fc095705

代码:

import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;

public class RebuildRSAPrivateKey2 {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        System.out.println("Rebuilding of a RSA PrivateKey from modulus & exponent");
        // rsa key generation
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        //kpGen.initialize(2048, new SecureRandom());
        kpGen.initialize(512, new SecureRandom()); // don't use 512 bit keys as they are insecure !!
        KeyPair keyPair = kpGen.generateKeyPair();
        // private key
        PrivateKey privateKey = keyPair.getPrivate();
        // get modulus & exponent
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
        BigInteger modulus = rsaPrivateKey.getModulus();
        BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
        // rebuild the private key
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
        PrivateKey privateKeyRebuild = keyFactory.generatePrivate(rsaPrivateKeySpec);
        System.out.println("privateKey equals rebuild: " + Arrays.equals(privateKey.getEncoded(), privateKeyRebuild.getEncoded()));
        // public key
        PublicKey publicKey = keyPair.getPublic();
        // get modulus & exponent
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        BigInteger modulusPub = rsaPublicKey.getModulus();
        BigInteger publicExponent = rsaPublicKey.getPublicExponent();
        // rebuild the public key
        KeyFactory keyFactoryPub = KeyFactory.getInstance("RSA");
        RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulusPub, publicExponent);
        PublicKey publicKeyRebuild = keyFactory.generatePublic(rsaPublicKeySpec);
        System.out.println("publicKey equals rebuild: " + Arrays.equals(publicKey.getEncoded(), publicKeyRebuild.getEncoded()));
        System.out.println("\nprivateKey original    encoded: " + bytesToHex(privateKey.getEncoded()));
        System.out.println("privateKey rebuild     encoded: " + bytesToHex(privateKeyRebuild.getEncoded()));
        RSAPrivateKey rsaPrivateKeyRebuild = (RSAPrivateKey) privateKeyRebuild;
        RSAPublicKey rsaPublicKeyRebuild = (RSAPublicKey) publicKeyRebuild;
        RSAPrivateCrtKey rsaPrivateCrtKey = createCrtKey(rsaPublicKeyRebuild, rsaPrivateKeyRebuild);
        System.out.println("privateKey rebuild CRT encoded: " + bytesToHex(rsaPrivateCrtKey.getEncoded()));
    }

    /**
     * 
     * answered Mar 31 '17 at 18:16 President James K. Polk
     * Find a factor of n by following the algorithm outlined in Handbook of Applied Cryptography, section
     * 8.2.2(i). See http://cacr.uwaterloo.ca/hac/about/chap8.pdf.
     *
     */

    private static BigInteger findFactor(BigInteger e, BigInteger d, BigInteger n) {
        BigInteger edMinus1 = e.multiply(d).subtract(BigInteger.ONE);
        int s = edMinus1.getLowestSetBit();
        BigInteger t = edMinus1.shiftRight(s);

        for (int aInt = 2; true; aInt++) {
            BigInteger aPow = BigInteger.valueOf(aInt).modPow(t, n);
            for (int i = 1; i <= s; i++) {
                if (aPow.equals(BigInteger.ONE)) {
                    break;
                }
                if (aPow.equals(n.subtract(BigInteger.ONE))) {
                    break;
                }
                BigInteger aPowSquared = aPow.multiply(aPow).mod(n);
                if (aPowSquared.equals(BigInteger.ONE)) {
                    return aPow.subtract(BigInteger.ONE).gcd(n);
                }
                aPow = aPowSquared;
            }
        }
    }

    public static RSAPrivateCrtKey createCrtKey(RSAPublicKey rsaPub, RSAPrivateKey rsaPriv) throws NoSuchAlgorithmException, InvalidKeySpecException {
        BigInteger e = rsaPub.getPublicExponent();
        BigInteger d = rsaPriv.getPrivateExponent();
        BigInteger n = rsaPub.getModulus();
        BigInteger p = findFactor(e, d, n);
        BigInteger q = n.divide(p);
        if (p.compareTo(q) > 0) {
            BigInteger t = p;
            p = q;
            q = t;
        }
        BigInteger exp1 = d.mod(p.subtract(BigInteger.ONE));
        BigInteger exp2 = d.mod(q.subtract(BigInteger.ONE));
        BigInteger coeff = q.modInverse(p);
        RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, exp1, exp2, coeff);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return (RSAPrivateCrtKey) kf.generatePrivate(keySpec);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }
}

执行 RSA 解密操作所需的最少信息是 modulus n 和解密指数 d。有一个优化可以应用于涉及中国剩余定理的 RSA 解密,其中求幂 mod RSA 素数分别进行,然后组合产生最终值,因此在RSA Private Key syntax and the Java RSAPrivateCrtKey 界面 mod 紧随其后。

现在这里提出的问题是:两个RSAPrivateCrtKey实例什么时候相等?我认为当它们在 RSA 算法中的功能相同时它们是相等的。您要求更狭义的定义,即当它们的编码形式相等时它们是相等的。这个定义的问题在于它过于特定于实现。 目前,当“Sun”提供者生成一个密钥对时,它总是对素数pq排序,这样p > q。但我喜欢另一种方式,其中 p < q。 RSAPrivateCrtKey 接口不关心任何一种方式,因为它不检查。接口的 Javadocs 没有指定顺序。您可以更改我的代码以生成与 current“Sun”实现相同的编码形式,只需反转 p.compareTo(q) > 0 中的比较即可。但是,默认实现可以更改以符合我未来的偏好,如果我接管世界的计划成功,它就会改变。 Javadocs是规范,实现可能会改变,只要符合Javadocs即可。

下面我提供了一个相等函数的实现,我在其中尝试纳入与规范一致的尽可能广泛的相等概念。也就是说,在 RSA 算法中使用 keyEquals returns true 时,任何两个 RSAPrivateCRTKey 实例都应该产生相同的结果,如果返回 false 那么至少应该有他们产生不同结果的一个值。

public static boolean keyEquals(RSAPrivateCrtKey k1, RSAPrivateCrtKey k2) {

    final BigInteger ZERO = BigInteger.ZERO;

    boolean result = true;

    result = result && isConsistent(k1) && isConsistent(k2);
    result = result && k1.getModulus().equals(k2.getModulus());
    BigInteger lambda = computeCarmichaelLambda(k1.getPrimeP(), k1.getPrimeQ());

    result = result && k1.getPublicExponent().subtract(k2.getPublicExponent()).mod(lambda).equals(ZERO);
    result = result && k1.getPrivateExponent().subtract(k2.getPrivateExponent()).mod(lambda).equals(ZERO);

    return result;
}

private static boolean isConsistent(RSAPrivateCrtKey k1) {
    final BigInteger ZERO = BigInteger.ZERO;
    final BigInteger ONE = BigInteger.ONE;

    BigInteger n = k1.getModulus();
    BigInteger p = k1.getPrimeP();
    BigInteger q = k1.getPrimeQ();
    BigInteger e = k1.getPublicExponent();
    BigInteger d = k1.getPrivateExponent();

    boolean result = true;

    result = p.multiply(q).equals(n);
    BigInteger lambda = computeCarmichaelLambda(p, q);
    result = result && e.multiply(d).mod(lambda).equals(ONE);
    result = result && d.subtract(key.getPrimeExponentP()).mod(p.subtract(ONE)).equals(ZERO);
    result = result && d.subtract(key.getPrimeExponentQ()).mod(q.subtract(ONE)).equals(ZERO);
    result = result && q.multiply(k1.getCrtCoefficient()).mod(p).equals(ONE);
    return result;
}

private static BigInteger computeCarmichaelLambda(BigInteger p, BigInteger q) {
    return lcm(p.subtract(BigInteger.ONE), q.subtract(BigInteger.ONE));
}

private static BigInteger lcm(BigInteger x, BigInteger y) {
    return x.multiply(y).divide(x.gcd(y));
}