Java RSA/ECB/PKCS1用.NET填充加密



我需要使用公钥加密网站上的密码。公钥是通过Java web服务提供的,其中包含以下信息:密钥是要与RSA/ECB/PKCS1Padding算法一起使用的RSA密钥。公钥以JSON形式传递,格式为:

{
    "kid":"PWD",
    "kty":"RSA",
    "use":"enc",
    "n":"MTA0OTgzNjg0OTMxMzE2NjkwNTU4Mjg3NDIwMDg1NTY0ODEyMjg1MDk2NTcwNzU5NDIzNzM0O
    DA3OTA2MzA0MDczNTU0NDQ2Njg3ODY2ODk2NTk0NjYzNTAxMzg0NzE1OTExMjA0MjU1MzMzOTIzMjA
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    zcwMjA3MzQxOTcwNzc4NDAwNzM3MTY2NDMyNzIwNjMwMDQwOTMwOTQ0MTA2OTE1NDEzMDAwNTMyMTE
    5ODM0MTA2MjAzMDIyODEwMjYyMDM3MDQ0NzkxNzIzNTU1MjQyNjYxMzE2ODc4OTc5NzY1OTgzMjg4M
    zQ0NDc3OTYwNTg3MzE2NTUwMDgx",
    "e":"NjU1Mzc"
 }

我尝试使用公钥加密密码,但生成的密钥不正确。这是我的代码:

encryptedPassword = EncrypIt("Password01", "MTA1MzQxNzIwODA3NjUwNzg5ND
Y4ODU2Mjc0NDA3ODIwMjQ1ODQ5NDE1MDk1MDIzMTM3MjM0NzAwNzYzNjc2MTgwNjg3ODMxMjA3
NTY0NTcxMjg2MzM4NjQ1NzEwMDcyMjY2MTQyNDIzMTczMjkwMDk0MTc0MTA5MTc5MzI0NjYwMjQ4NzI3NzM0MTQ5NDY0MjUwODkwO
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
c2OTAzNjc3NzQzODM3NzM0MjE2ODM0NjY4MjM4MTQ0OTA3MDQ3MTk1Njc1NzU3OTE2NjEyNzkzMzM2MzI3MDUyNjg0NDI5NDExNjI
2MzA0MzM5", "NjU1Mzc");
    public static string EncrypIt(string password, string publicKey, string exponent)
    {
        UnicodeEncoding ByteConverter = new UnicodeEncoding();
        byte[] publicKeyByte = ByteConverter.GetBytes(publicKey);
        byte[] passwordByte = ByteConverter.GetBytes(password);
        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        RSAParameters RSAKeyInfo = new RSAParameters();
        RSAKeyInfo = RSA.ExportParameters(false); //Export only public key
        //Set RSAKeyInfo to the public key values. 
        RSAKeyInfo.Modulus = publicKeyByte;
        //RSAKeyInfo.Exponent = exponentBytes; //I tried to set exponent but I have an error
        RSA.ImportParameters(RSAKeyInfo);
        byte[] encryptedPassword = RSA.Encrypt(passwordByte, false);
        return Convert.ToBase64String(encryptedPassword);
    }

(JSON和我的代码的公钥不同,但不要注意,我只是从不同的来源复制了信息)

  • 我获得的加密密码太长了:加密密码应该是172个字符(我知道,因为我有一个小Java程序可以正确加密密码),但我得到了1100个字符
  • 我不使用指数:我应该吗
  • 我不能使用JSON直接正确配置RSACryptoServiceProvider吗

owlstead的答案帮助我获得了一个具有正确字符串大小的加密密码,但使用加密字符串的服务拒绝了它,并显示消息:javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes

我测试了正在进行正确加密的java程序的代码(见下文)。我需要使用.NET.实现相同的加密

public class EncryptionServiceImpl
{
    private static final Charset UTF8 = Charset.forName("UTF-8");
    @Resource(name = "briqueAuthentificationClient")
    private BriqueAuthentificationClientImpl briqueAuthentificationClient;
    protected static final String ALGORITHM_RSA = "RSA";
    protected static final String TRANSFORMATION_RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding";
    private static final Logger LOG = LoggerFactory.getLogger(EncryptionServiceImpl.class);
    public EncryptionServiceImpl() {
        LOG.info("constructeur EncryptionServiceImpl");
    }
    /**
     * @param briqueAuthentificationClient the briqueAuthentificationClient to set
     */
    public void setBriqueAuthentificationClient(final BriqueAuthentificationClientImpl briqueAuthentificationClient) {
        this.briqueAuthentificationClient = briqueAuthentificationClient;
    }
    public String encrypt(final String input) throws GeneralSecurityException {
        if (StringUtils.isNotBlank(input)) {
            final CertificateDto certificate = this.briqueAuthentificationClient.getCurrentCertificate();
            if (certificate != null) {
                return new String(this.encryptAndEncode(input.getBytes(), certificate), EncryptionServiceImpl.UTF8);
            } else {
                throw new RuntimeException("Certificate is null");
            }
        }
        return null;
    }
    protected byte[] encryptAndEncode(final byte[] input, final CertificateDto currentCertificate)
            throws GeneralSecurityException {
        // Création de la clé publique
        final PublicKey publicKey = this.buildPublicKey(currentCertificate);
        // Chiffre
        final byte[] inputEncrypted = this.encrypte(input, publicKey);
        // Encode
        return this.encodeBase64Url(inputEncrypted);
    }
    protected PublicKey buildPublicKey(final CertificateDto currentCertificate) throws GeneralSecurityException {
        if ("RSA".equals(currentCertificate.getKeyType())) {
            return this.buildRSAPublicKey(currentCertificate);
        }
        LOG.error(String.format("Tentative de création d'une clé publique avec un algorithme non connu [%s]",
                currentCertificate.getKeyType()));
        return null;
    }
    protected PublicKey buildRSAPublicKey(final CertificateDto currentCertificate) throws GeneralSecurityException {
        final BigInteger modulus = new BigInteger(new String(Base64.decodeBase64(currentCertificate.getModulus()),
                EncryptionServiceImpl.UTF8));
        final BigInteger publicExponent = new BigInteger(new String(Base64.decodeBase64(currentCertificate
                .getPublicExponent()), EncryptionServiceImpl.UTF8));
        try {
            return KeyFactory.getInstance(ALGORITHM_RSA).generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
        } catch (InvalidKeySpecException e) {
            throw e;
        } catch (NoSuchAlgorithmException e) {
            throw e;
        }
    }
    protected byte[] encrypte(final byte[] input, final RSAPublicKeySpec rsaPublicKeySpec)
            throws GeneralSecurityException {
        PublicKey publicKey;
        try {
            publicKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePublic(
                    new RSAPublicKeySpec(rsaPublicKeySpec.getModulus(), rsaPublicKeySpec.getPublicExponent()));
        } catch (InvalidKeySpecException e) {
            throw e;
        } catch (NoSuchAlgorithmException e) {
            throw e;
        }
        return this.encrypte(input, publicKey);
    }
    protected byte[] encrypte(final byte[] input, final PublicKey publicKey) throws GeneralSecurityException {
        try {
            final Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA_ECB_PKCS1PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(input);
        } catch (NoSuchAlgorithmException e) {
            throw e;
        } catch (NoSuchPaddingException e) {
            throw e;
        } catch (IllegalBlockSizeException e) {
            throw e;
        } catch (BadPaddingException e) {
            throw e;
        }
    }
    protected byte[] decrypte(final byte[] input, final RSAPrivateKeySpec rsaPrivateKeySpec)
            throws GeneralSecurityException {
        final BigInteger modulus = rsaPrivateKeySpec.getModulus();
        final BigInteger privateExponent = rsaPrivateKeySpec.getPrivateExponent();
        try {
            final PrivateKey privateKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePrivate(
                    new RSAPrivateKeySpec(modulus, privateExponent));
            final Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA_ECB_PKCS1PADDING);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(input);
        } catch (NoSuchAlgorithmException e) {
            throw e;
        } catch (NoSuchPaddingException e) {
            throw e;
        } catch (IllegalBlockSizeException e) {
            throw e;
        } catch (BadPaddingException e) {
            throw e;
        } catch (InvalidKeySpecException e) {
            throw e;
        } catch (InvalidKeyException e) {
            throw e;
        }
    }
    protected byte[] encodeBase64Url(final byte[] input) {
        return Base64.encodeBase64(input, false);
    }
    protected byte[] decodeBase64Url(final byte[] input) {
        return Base64.decodeBase64(input);
    }
    /**
     * Method to connect to an url
     * 
     * @param httpclient the http connection
     * @return the response GetMethod
     * @throws OAuthException in cas of connection error
     */
    private GetMethod connect(final HttpClient httpclient, final String url) {
        final GetMethod httpget = new GetMethod(url);
        try {
            httpclient.executeMethod(httpget);
        } catch (final UnknownHostException e) {
            throw new RuntimeException("Connection ERROR - Host could not be determined.", e);
        } catch (final IOException e) {
            throw new RuntimeException("Connection ERROR - Input/Output error.", e);
        }
        return httpget;
    }
}

我在owlstead的帮助下完成的步骤如下。当我使用这个Java程序对字符串进行编码时Password01我会得到一个字符串,比如:

sy5/XElHvuYA4Rbq8OBydLymT82R+z77jy6MU2sNal7VenzPEbARgy7p3zWgYgG13Cypk+zbnnB5L37fVUhgOgCqCyLtikzxJBNmPyzUK9+beiHJKyONZwifDzQ44hXTeXcZ0bmF9b5dLFy9QS/N5m28vIyBSGY8K2B7EB2FF38=  

这个加密的密码可以在Java端上解密

当我使用.NET代码时,字符串如下:

ACZXYj/KudyxKBB510SxSouKaVwssmEUM6Jpreh8jTtrIH9eGb18GIdkBU7rXzMuLYbAhyREbFLbR87zW/2DNa4tN97FOlqr6k1JppJ/SSS/9fGdMvSOAQbWjsxksDH7fRu9dCvK0m0exFtGfXG7Yua9bB1m0dTNiwCZUZM0LnEM  

此加密密码无法在Java端解密。失败,返回错误消息:

javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes

您首先需要使用ne中的Convert.FromBase64String执行base64解码,将ASCII编码的结果转换为字符串,然后使用BigInteger.parse解析结果。然后可以使用toByteArray将其转换为字节,但要注意BigInteger是小端序,而RSAParameters需要大端序,因此必须反转字节。

您的.NET RSA密文(可能不应该反转)前面有一个值为00h的字节,这使它成为无效密文。RSA密文必须具有密钥的确切长度(以字节为单位)。

谢谢各位的回答和评论,它帮助我最终解决了我的问题:

对于指数,我得到一条错误消息:Base-64字符数组或字符串的长度无效这是因为基数64的值应该是4的倍数。如果不是这种情况,我们应该附加等号(=)以达到4的倍数。因此,在将指数串从"NjU1Mzc"改变为"NjU1Mzc="之后,可以对该值进行解码。

然后我应用了owlstead提供的解决方案。这是工作正常的最后一个代码:

//Decode from base 64
byte[] publicKeyByte = Convert.FromBase64String(rsaPublicKey.modulo);
byte[] exponentByte = Convert.FromBase64String(rsaPublicKey.exponent);
//Convert to ASCII string
UTF8Encoding ByteConverter = new UTF8Encoding();
string publicKeyString = System.Text.Encoding.Default.GetString(publicKeyByte);
string exponentString = System.Text.Encoding.Default.GetString(exponentByte);
//Convert to BigInteger
BigInteger publicKeyBI = BigInteger.Parse(publicKeyString);
BigInteger exponentBI = BigInteger.Parse(exponentString);
//Convert back to byte array
byte[] publicKeyByte2 = publicKeyBI.ToByteArray();
byte[] exponentByte2 = exponentBI.ToByteArray();
//We must remove the 129th sign byte which is added when converting to BigInteger
if (publicKeyByte2.Length == 129) Array.Resize(ref publicKeyByte2, 128);
//Create crypto service
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
//Assign RSA key modulus/exponent reversing from little endian to big endian
RSAKeyInfo.Modulus = publicKeyByte2.Reverse().ToArray();
RSAKeyInfo.Exponent = exponentByte2.Reverse().ToArray();
RSA.ImportParameters(RSAKeyInfo);
//Convert password string to byte array
byte[] passwordByte = ByteConverter.GetBytes(clearPassword);
//Encrypt the password and encode 64
encryptedPassword = Convert.ToBase64String(RSA.Encrypt(passwordByte, false));

owlstead缺少的一点是:该方法返回一个带有额外元素的字节数组,该元素的值为零,以消除正值的歧义有关这一点的更多信息,请参阅Microsoft文档:BigInteger.ToByteArray方法

这段代码将密码加密为一个172个字符的字符串,该字符串以=符号结尾,这是我所期望的,并且在Java端使用私钥正确解密。

我试过了,它正确地加密了

    var input = "String to Encode.";
    var mod = "MTA1MzQxNzIwODA3NjUwNzg5NDY4ODU2Mjc0NDA3ODIwMjQ1ODQ5NDE1MDk1MDIzMTM3MjM0NzAwNzYzNjc2MTgwNjg3ODMxMjA3NTY0NTcxMjg2MzM4NjQ1NzEwMDcyMjY2MTQyNDIzMTczMjkwMDk0MTc0MTA5MTc5MzI0NjYwMjQ4NzI3NzM0MTQ5NDY0MjUwODkwOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXc2OTAzNjc3NzQzODM3NzM0MjE2ODM0NjY4MjM4MTQ0OTA3MDQ3MTk1Njc1NzU3OTE2NjEyNzkzMzM2MzI3MDUyNjg0NDI5NDExNjI2MzA0MzM5==";
    var exp = "NjU1Mzc=";
    var intValue = int.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(exp)));
    var rsaParam = new RSAParameters();
    rsaParam.Modulus = Convert.FromBase64String(mod);
    rsaParam.Exponent = BitConverter.GetBytes(intValue);
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(rsaParam);
        Console.WriteLine(Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(input), false)));
    }
    Console.ReadLine();

我认为问题是指数很奇怪。Exp=65537;

最新更新