简体   繁体   中英

Java RSA/ECB/PKCS1Padding encryption with .NET

I need to encrypt a password on a web site using a public key. The public key is delivered through a Java web service with the information: The key is a RSA key to be used with RSA/ECB/PKCS1Padding algorithm. The public key is delivered as JSON in the form:

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

I tried to encrypt the password using the public key but the key generated is not correct. Here is my code:

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

(The public key is different between the JSON and my code but don't pay attention to it, I just copied information from different sources)

  • The encrypted password I obtained is far too long: The encrypted password should be 172 characters (I know because I have a small Java program that allows me to encrypt passwords correctly) but I get 1100 characters.
  • I do not use the exponent: Should I ?
  • Could I not use the JSON to directly configure the RSACryptoServiceProvider correctly ?

The answer from owlstead helped me to get an encrypted password with the right string size but the service using the encrypted string reject it with the message: javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes

I otained the code of the java program wich is doing the correct encryption (see below). I need to achieve the same encryption using .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;
    }

}

The steps I accomplished with the help of owlstead are in the answer below. When I use this Java program to encode the string Password01 I obtain a string like:

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

This encrypted password can be decrypted on Java side

When I use the .NET code the string is like:

ACZXYj/KudyxKBB510SxSouKaVwssmEUM6Jpreh8jTtrIH9eGb18GIdkBU7rXzMuLYbAhyREbFLbR87zW/2DNa4tN97FOlqr6k1JppJ/SSS/9fGdMvSOAQbWjsxksDH7fRu9dCvK0m0exFtGfXG7Yua9bB1m0dTNiwCZUZM0LnEM  

This encrypted password failed to be decrypted on Java side. Failed with the error message:

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

You first need to perform base 64 decoding using Convert.FromBase64String of n and e , convert the result from ASCII encoding to a string and then parse the result using BigInteger.parse . Then you can convert using toByteArray to bytes, but beware that BigInteger is little endian, and RSAParameters expects big endian , so you have to reverse the bytes.

Your .NET RSA ciphertext (which you probably shouldn't reverse) is preceded by a 00h valued byte, which makes it an invalid ciphertext. RSA ciphertext must be of the exact length in bytes of the key.

Thank you sirs for the answers and comments, it help me to finally solve my issue:

For exponent, I got an error message: Invalid length for a Base-64 char array or string This is because a base 64 value should be a multiple of 4. If this is not the case we should append equal sign (=) to reach a multiple of 4. Thus after changing the exponent string from "NjU1Mzc" to "NjU1Mzc=" the value can be decoded.

Then I applied the solution provided by owlstead. Here is the final code which works fine:

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

The missing point from owlstead is that: the method returns a byte array with an extra element whose value is zero to disambiguate positive values See Microsoft documentation for more information on this point: BigInteger.ToByteArray Method

This code encrypts the password as a 172 characters string which ends with a = sign which is what I expected and it is correctly decrypted on Java side with the private key.

I tried this and it encrypts correctly

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

I think the issue is the exponent is strange. Exp = 65537;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM