使用.NET的Java RSA / ECB / PKCS1Padding加密

时间:2014-10-22 15:16:25

标签: c# .net encryption

我需要使用公钥加密网站上的密码。公钥通过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

3 个答案:

答案 0 :(得分:3)

您首先需要使用ne的{​​{3}}执行base 64解码,将ASCII编码的结果转换为字符串,然后解析结果使用Convert.FromBase64String。然后你可以使用toByteArray转换为字节,但要注意BigInteger是小端,BigInteger.parse,所以你必须反转字节。

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

答案 1 :(得分:1)

感谢先生们的回答和评论,它帮助我最终解决了我的问题:

对于exponent,我收到一条错误消息:Base-64 char数组或字符串的长度无效 这是因为基数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 Method

此代码将密码加密为172个字符的字符串,该字符串以=符号结束,这符合我的预期,并且使用私钥在Java端正确解密。

答案 2 :(得分:0)

我试过这个并正确加密

    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;

相关问题