So I have some code in Java for encrypting and decrypting objects and I've been trying to do the same in C# so my server and client can decrypt and encrypt messages. At the moment the last piece of the puzzle I need to fix is my C# decryption. When attempting to decrypt, I usually get a bad padding exception of sorts. However, Java is using PCKS5 padding and C# is using PCKS7 padding which if I understand correctly are virtually indistinguishable.
#RequiredArgsConstructor
#Getter
public class EncryptedBytes {
private final byte[] data;
private final byte[] params;
private final String paramAlgorithm;
}
/**
* Encrypt object with password
*
* #param data Object to be encrypted
* #param secret Password to use for encryption
* #return Encrypted version of object
*/
public static EncryptedBytes encrypt(String data, SecretKey secret) throws InvalidKeyException {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
// properly encode the complete ciphertext
//logEncrypt(password, object);
byte[] encodedData = cipher.doFinal(data.getBytes(CharsetUtil.UTF_8));
byte[] params = cipher.getParameters().getEncoded();
String paramAlgorithm = cipher.getParameters().getAlgorithm();
return new EncryptedBytes(encodedData, params, paramAlgorithm);
} catch (InvalidKeyException e) {
throw e;
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | BadPaddingException | IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Decrypt data with secret
*
* #param encryptedBytes Object to be decrypted
* #param secret Password to use for decryption
* #return Decrypted version of object
*/
public static String decrypt(EncryptedBytes encryptedBytes, #NonNull SecretKey secret) throws InvalidKeyException {
try {
// get parameter object for password-based encryption
AlgorithmParameters algParams = AlgorithmParameters.getInstance(encryptedBytes.getParamAlgorithm()); // getParamAlgorithm usually just returns "AES"
if (algParams == null) throw new IllegalArgumentException("EncryptedBytes.Parameters are not valid");
// initialize with parameter encoding from above
algParams.init(encryptedBytes.getParams());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, algParams);
return new String(cipher.doFinal(encryptedBytes.getData()), CharsetUtil.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | IOException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
Now in C# I have a very close implementation to the Java code above.
public class EncryptedBytes
{
[NotNull]
public List<sbyte> data { get; }
[NotNull]
[JsonProperty("params", Required = Required.Always)]
[JsonPropertyName("params")]
public List<sbyte> keyParams { get; }
[NotNull]
public string paramAlgorithm { get; }
public EncryptedBytes(IEnumerable<sbyte> data, [JsonProperty(propertyName: "params")] IEnumerable<sbyte> keyParams, string paramAlgorithm)
{
this.data = data.ToList();
this.keyParams = keyParams.ToList();
this.paramAlgorithm = paramAlgorithm;
}
}
public static EncryptedBytes encrypt(AesCryptoServiceProvider aesCryptoServiceProvider, string plainText,
Encoding encoding)
{
if (encoding == null) encoding = StaticHandler.encoding; // UTF_8
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (aesCryptoServiceProvider.Key == null || aesCryptoServiceProvider.Key.Length <= 0)
throw new ArgumentNullException("Key");
if (aesCryptoServiceProvider.IV == null || aesCryptoServiceProvider.IV.Length <= 0)
throw new ArgumentNullException("IV");
ICryptoTransform transform = aesCryptoServiceProvider.CreateEncryptor();
var encodedText = encoding.GetBytes(plainText);
var encryptedText =
transform.TransformFinalBlock(encodedText, 0, encodedText.Length).Select(Convert.ToSByte);
var derOctetString = new DerOctetString(aesCryptoServiceProvider.IV);
return new EncryptedBytes(encryptedText, derOctetString.GetEncoded().Select(Convert.ToSByte), "AES");
}
public static string decrypt([NotNull] EncryptedBytes encryptedBytes, AesCryptoServiceProvider aesCryptoServiceProvider, Encoding? encoding)
{
if (encoding == null) encoding = StaticHandler.encoding;
var cipherText = encryptedBytes.data.Select(arg => (byte) arg).ToArray();
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (aesCryptoServiceProvider.Key == null || aesCryptoServiceProvider.Key.Length <= 0)
throw new ArgumentNullException("Key");
if (aesCryptoServiceProvider.IV == null || aesCryptoServiceProvider.IV.Length <= 0)
throw new ArgumentNullException("IV");
// Declare the string used to hold
// the decrypted text.
aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
var transform = aesCryptoServiceProvider.CreateDecryptor();
string plaintext;
var keyParams = encryptedBytes.keyParams.Select(b => (byte) b).ToArray();
var octetString = (DerOctetString) Asn1Object.FromByteArray(keyParams);
aesCryptoServiceProvider.IV = octetString.GetOctets();
aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
plaintext = encoding.GetString(transform.TransformFinalBlock(cipherText, 0, cipherText.Length));
return plaintext;
}
The main code in question is this:
var transform = aesCryptoServiceProvider.CreateDecryptor();
string plaintext;
var keyParams = encryptedBytes.keyParams.Select(b => (byte) b).ToArray();
var octetString = (DerOctetString) Asn1Object.FromByteArray(keyParams);
aesCryptoServiceProvider.IV = octetString.GetOctets();
aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
plaintext = encoding.GetString(transform.TransformFinalBlock(cipherText, 0, cipherText.Length));
For testing purposes, you may use this value as an example of data that is parsed (EncryptedBytes class converted to JSON)
{"data":[-91,-105,-48,-75,18,-64,-12,-122,56,76,17,101,54,65,53,-10],"params":[4,16,81,-32,-95,-80,-81,100,-81,58,49,-36,35,21,-115,-110,111,-2],"paramAlgorithm":"AES"}
This should just return empty brackets as plaintext.
Expected result:
{}
You may use this key: (both values are the same key, written differently)
Byte array: 15,215,133,73,75,92,34,113,122,135,36,209,240,159,54,165,30,23,86,73,166,7,166,192,209,145,68,167,19,205,105,55
Base64: D9eFSUtcInF6hyTR8J82pR4XVkmmB6bA0ZFEpxPNaTc=
Related
I am trying to encrypt and decrypt my messages from my android app and been successful with the below code.
I want to decrypt this message in my c# based web application service. Can anyone please help me out with the equivalent code in c# for the below encryption code**.
this is the encrypting code
// encrypt the message
byte[] encryptedMsg = encryptSMS(secretKeyString,
msgContentString);
// convert the byte array to hex format in order for
// transmission
String msgString = byte2hex(encryptedMsg);
// send the message through SMS
sendSMS(recNumString, msgString);
// finish
finish();
} else
Toast.makeText(getBaseContext(),"Please enter phone number, secret key and the message. Secret key must be 16 characters!",Toast.LENGTH_SHORT).show();
}
});
}
public static void sendSMS(String recNumString, String encryptedMsg) {
try {
// get a SmsManager
SmsManager smsManager = SmsManager.getDefault();
// Message may exceed 160 characters
// need to divide the message into multiples
ArrayList<String> parts = smsManager.divideMessage(encryptedMsg);
smsManager.sendMultipartTextMessage(recNumString, null, parts,
null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
// utility function
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xFF);
if (stmp.length() == 1)hs += ("0" + stmp);
else hs += stmp;
}
return hs.toUpperCase();
}
// encryption function
public static byte[] encryptSMS(String secretKeyString,
String msgContentString) {
try {
byte[] returnArray;
// generate AES secret key from user input
Key key = generateKey(secretKeyString);
// specify the cipher algorithm using AES
Cipher c = Cipher.getInstance("AES");
// specify the encryption mode
c.init(Cipher.ENCRYPT_MODE, key);
// encrypt
returnArray = c.doFinal(msgContentString.getBytes());
return returnArray;
} catch (Exception e) {
e.printStackTrace();
byte[] returnArray = null;
return returnArray;
}
}
private static Key generateKey(String secretKeyString) throws Exception {
Key key = new SecretKeySpec(secretKeyString.getBytes(), "AES");
return key;
}
}
and this is the decrypting code
// utility function: convert hex array to byte array
public static byte[] hex2byte(byte[] b) {
if ((b.length % 2) != 0) throw new IllegalArgumentException("hello");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += 2) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
// decryption function
public static byte[] decryptSMS(String secretKeyString, byte[] encryptedMsg)
throws Exception {
// generate AES secret key from the user input secret key
Key key = generateKey(secretKeyString);
// get the cipher algorithm for AES
Cipher c = Cipher.getInstance("AES");
// specify the decryption mode
c.init(Cipher.DECRYPT_MODE, key);
// decrypt the message
byte[] decValue = c.doFinal(encryptedMsg);
return decValue;
}
private static Key generateKey(String secretKeyString) throws Exception {
// generate AES secret key from a String
Key key = new SecretKeySpec(secretKeyString.getBytes(), "AES");
return key;
}
}
I need to securely persist AES key(s) to be used by the .NET AesCng algorithm. The idea is to use the CngKey class to persist the key(s) and leverage its export/import functionality to maintain the same key(s) across multiple servers.
I can create the persisted AES key
public static bool CreateContainer(string name)
{
if (CngKey.Exists(name))
{
return false;
}
CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey
};
CngKey cngKey = CngKey.Create(new CngAlgorithm("AES"), name, keyCreationParameters);
cngKey.Dispose();
return true;
}
and then use it to encrypt/decrypt
public static byte[] Encrypt(string keyContainerName, byte[] clearText, byte[] iv)
{
AesCng aesCng = null;
ICryptoTransform crypto = null;
byte[] cipher = null;
try
{
aesCng = new AesCng(keyContainerName);
aesCng.IV = (iv == null ? new byte[aesCng.IV.Length] : iv);
crypto = aesCng.CreateEncryptor();
cipher = crypto.TransformFinalBlock(clearText, 0, clearText.Length);
}
finally
{
if (crypto != null)
{
crypto.Dispose();
}
if (aesCng != null)
{
aesCng.Clear();
aesCng.Dispose();
}
}
return cipher;
}
public static byte[] Decrypt(string keyContainerName, byte[] cipher, byte[] iv)
{
AesCng aesCng = null;
ICryptoTransform crypto = null;
byte[] clearText = null;
try
{
aesCng = new AesCng(keyContainerName);
aesCng.IV = (iv == null ? new byte[aesCng.IV.Length] : iv);
crypto = aesCng.CreateDecryptor();
clearText = crypto.TransformFinalBlock(cipher, 0, cipher.Length);
}
finally
{
if (crypto != null)
{
crypto.Dispose();
}
if (aesCng != null)
{
aesCng.Clear();
aesCng.Dispose();
}
}
return clearText;
}
I am able to export the key
public static bool ExportKey(string name, out byte[] blob)
{
blob = null;
if (!CngKey.Exists(name))
{
return false;
}
CngKey cngKey = CngKey.Open(name);
blob = cngKey.Export(CngKeyBlobFormat.OpaqueTransportBlob);
cngKey.Dispose();
return true;
}
However, when I try to import the blob, I get a CryptographicException: The supplied handle is invalid.
public static void ImportKey(string name, byte[] blob)
{
CngKey cngKey = CngKey.Import(blob, CngKeyBlobFormat.OpaqueTransportBlob);
cngKey.Dispose();
}
I am at a loss to explain why the failure.
Can anyone shed some light on this?
Thanks.
I am using Android and I am trying to decrypt a message encrypted in a C Sharp Server.
Below is the code for the C# Cryptor, that uses 256 bit long Keys, 128 bit long IV, 5000 Iterations. It uses Rfc2898DeriveBytes Class, so that is the same as PBKDF2WithHmacSHA1 in Android.
The decrypt function of the C# Cryptor takes as its IV the (reversed) first 128 bits of the 256 bit long key.
namespace CompanyName.Framework.Encryption
{
internal class SymmetricCryptor : ISymmetricCryptor
{
internal static int KeyLengthInBytes = 32;
internal int Iterations = 5000;
#region Private Fields
// RijndaelManaged aes; old version
AesManaged aes;
int IVLength = KeyLengthInBytes >> 1;
#endregion Private Fields
#region Internal Constructors
internal SymmetricCryptor( )
{
aes = new AesManaged
{
Mode = CipherMode.CBC,
KeySize= KeyLengthInBytes<<3,
Padding = PaddingMode.PKCS7,
};
//aes.KeySize = KeyLengthInBytes << 3;
//aes.Padding = PaddingMode.Zeros; //PKCS7 can not be used with stream
}
#endregion Internal Constructors
#region Public Methods
public byte[] Decrypt(byte[] cryptedData, string password, IVMode ivmode)
{
using (MemoryStream ms = new MemoryStream(cryptedData))
{
using (MemoryStream data = new MemoryStream())
{
Decrypt(ms, data, password,ivmode);
return data.ToArray();
}
}
}
public void Encrypt(Stream data, Stream trgStream, string password, IVMode ivmode)
{
try
{
var key = GetKey(password);
var iv = (ivmode == IVMode.Auto)
?key.GetBytes(IVLength).Reverse().ToArray()
: new byte[IVLength];
var dc = aes.CreateEncryptor(key.GetBytes(KeyLengthInBytes), iv);
using (CryptoStream cryptor = new CryptoStream(trgStream, dc, CryptoStreamMode.Write))
{
data.CopyTo(cryptor);
cryptor.FlushFinalBlock();
cryptor.Close();
}
}
catch (Exception)
{
throw new InvalidOperationException("Invalid password.");
}
}
public void Decrypt(Stream cryptedData, Stream trgStream, string password, IVMode ivmode)
{
try
{
var key= GetKey(password);
var iv = (ivmode == IVMode.Auto)
? key.GetBytes(IVLength).Reverse().ToArray()
: new byte[IVLength];
var dc = aes.CreateDecryptor(key.GetBytes(KeyLengthInBytes),iv);
using (CryptoStream cryptor = new CryptoStream(cryptedData, dc, CryptoStreamMode.Read))
{
cryptor.CopyTo(trgStream);
cryptor.Close();
}
}
catch (Exception)
{
throw new InvalidOperationException("Invalid password.");
}
}
public byte[] Encrypt(byte[] data, string password, IVMode ivmode)
{
using (MemoryStream ms = new MemoryStream(data))
{
using (MemoryStream cData = new MemoryStream())
{
Encrypt(ms, cData, password,ivmode);
return cData.ToArray();
}
}
}
#endregion Public Methods
#region Private Methods
private Rfc2898DeriveBytes GetKey(string password)
{
try
{
var iv =
CompanyName.Framework.Cryptography.Digest.SHA1.Compute(password);
return new Rfc2898DeriveBytes(password, iv, Iterations);
}
catch (Exception)
{
throw;
}
}
#endregion Private Methods
}
}
My Android Cryptor, which tries to decrypt a message encrypted by the above C Sharp Cryptor looks like this, I tried to copy the Decrypt method of the C Sharp Cryptor:
public class Cryptor {
private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding";
private static final String AES = "AES";
private static final String RANDOM_ALGO = "SHA1PRNG";
private static final int KEY_LENGTH_IN_BITS = 256;
private static final int IV_LENGTH = 16;
private static final int PBE_ITERATION_COUNT = 5000;
private static final int PBE_SALT_LENGTH_INT_BITS = 128;
private static final String PBE_ALGO = "PBKDF2WithHmacSHA1";
public static byte[] generateKeyFromPassword(String password, int Size) throws GeneralSecurityException {
byte[] salt = generateSalt();
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, PBE_ITERATION_COUNT, Size);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBE_ALGO);
byte[] data = keyFactory.generateSecret(keySpec).getEncoded();
return data;
}
private static byte[] generateSalt() throws GeneralSecurityException {
return randomBytes(PBE_SALT_LENGTH_INT_BITS);
}
private static byte[] randomBytes(int length) throws GeneralSecurityException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGO);
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
public static byte[] decrypt(byte[] cipherText, String password) throws GeneralSecurityException {
byte[] keyBytes = generateKeyFromPassword(password, 256);
byte[] ivBytes = generateKeyFromPassword(password, 128);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
ivBytes = reverse(ivBytes);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decrypted = cipher.doFinal(cipherText);
return decrypted;
}
public static byte[] reverse(byte[] array) {
if (array == null) {
return null;
}
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array;
}
But it is not working, When do final is called I get a
javax.crypto.BadPaddingException: error:1e06b065:Cipher functions:EVP_DecryptFinal_ex:BAD_DECRYPT
Exception. I am not sure what I am doing wrong, because my Decrypt Method in Android is doing the exact same thing as the Decrypt Method in C Sharp: First I generate a Key from the password, which is shared by the Csharp Server and me. Then I generate a random 128 bit IV, reversing it is not necessary, but C Sharp implementation reverses it, so I do it as well. Can anyone tell me what I am doing wrong? Here is the context where I use the Cryptor:
//open the client channel, read and return the response as byte[]
Channel clientChannel = new Channel(serverAddress);
byte[] result = clientChannel.execute(serviceID.toString(), data);
//result[] is encrypted data. firstTen is the shared Password
byte[] decrypted = Cryptor.decrypt(result, firstTen);
Server returns the result as Base64 encrypted, before passing it for decryption I get the result[] array through:
It comes as a Base64 String. I get the result[] array through:
Base64.decode(result, Base64.NO_WRAP);
You need to generate random salt and IV on Server side and sent it with ciphreText to Android side. Android need to use exactly the same salt and IV to derive key for decryption that that was used to derive encryption key on Server side.
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;
I have been given a Java implementation for encryption but unfortunately we are a .net shop and I have no way of incorporating the Java into our solution. Sadly, I'm also not a Java guy so I've been fighting with this for a few days and thought I'd finally turn here for help.
I've searched high and low for a way to match the way the Java encryption is working and I've come to the resolution that I need to use RijndaelManaged in c#. I'm actually really close. The strings that I'm returning in c# are matching the first half, but the second half are different.
Here is a snippet of the java implementation:
private static String EncryptBy16( String str, String theKey) throws Exception
{
if ( str == null || str.length() > 16)
{
throw new NullPointerException();
}
int len = str.length();
byte[] pidBytes = str.getBytes();
byte[] pidPaddedBytes = new byte[16];
for ( int x=0; x<16; x++ )
{
if ( x<len )
{
pidPaddedBytes[x] = pidBytes[x];
}
else
{
pidPaddedBytes[x] = (byte) 0x0;
}
}
byte[] raw = asBinary( theKey );
SecretKeySpec myKeySpec = new SecretKeySpec( raw, "AES" );
Cipher myCipher = Cipher.getInstance( "AES/ECB/NoPadding" );
cipher.init( Cipher.ENCRYPT_MODE, myKeySpec );
byte[] encrypted = myCipher.doFinal( pidPaddedBytes );
return( ByteToString( encrypted ) );
}
public static String Encrypt(String stringToEncrypt, String key) throws Exception
{
if ( stringToEncrypt == null ){
throw new NullPointerException();
}
String str = stringToEncrypt;
StringBuffer result = new StringBuffer();
do{
String s = str;
if(s.length() > 16){
str = s.substring(16);
s = s.substring(0,16);
}else {
str = null;
}
result.append(EncryptBy16(s,key));
}while(str != null);
return result.toString();
}
I'm not entirely sure why they're only passing in 16 chars at a time, but w/e. I tried the same with my c# implementation using a string builder and only sending in 16 chars at a time and got the same result as I got when I pass the entire string in at once.
Here's a snippet of my c# implementation which is mostly a copy and paste from MS' site for RijndaelManaged:
public static string Encrypt(string stringToEncrypt, string key)
{
using (RijndaelManaged myRijndael = new RijndaelManaged())
{
myRijndael.Key = StringToByte(key);
myRijndael.IV = new byte[16];
return EncryptStringToBytes(stringToEncrypt, myRijndael.Key, myRijndael.IV);
}
}
static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
return ByteToString(encrypted);
}
As I said above, the first half of the encrypted string is the same (see an example below), but the second half is off. I've added spaces in the outputs below to better illustrate where the difference is. I don't know enough about encryption nor Java to know where to turn next. Any guidance would be greatly appreciated
Java output:
49a85367ec8bc387bb44963b54528c97 8026d7eaeff9e4cb7cf74f8227f80752
C# output:
49a85367ec8bc387bb44963b54528c97 718f574341593be65034627a6505f13c
Update per the suggestion of Chris below:
static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
rijAlg.Padding = PaddingMode.None;
rijAlg.Mode = CipherMode.ECB;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
if (plainText.Length < 16)
{
for (int i = plainText.Length; i < 16; i++)
{
swEncrypt.Write((byte)0x0);
}
}
}
encrypted = msEncrypt.ToArray();
}
}
}
return ByteToString(encrypted);
}
Great question, this is a common error while working with the same encryption algorithm but in different languages. The implementation of the algorithm details requires attention. I haven't tested the code but in your case the padding options of two implementations are different, try to use the same padding options for both c# and java implementations.
You can read the comments and more about the implementation from here.
Please pay attention to the padding defaults.
Padding = PaddingMode.PKCS7,
private final String cipherTransformation = "AES/CBC/PKCS5Padding";
c# implementation:
public RijndaelManaged GetRijndaelManaged(String secretKey)
{
var keyBytes = new byte[16];
var secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
Array.Copy(secretKeyBytes, keyBytes, Math.Min(keyBytes.Length, secretKeyBytes.Length));
return new RijndaelManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
KeySize = 128,
BlockSize = 128,
Key = keyBytes,
IV = keyBytes
};
}
public byte[] Encrypt(byte[] plainBytes, RijndaelManaged rijndaelManaged)
{
return rijndaelManaged.CreateEncryptor()
.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
}
public byte[] Decrypt(byte[] encryptedData, RijndaelManaged rijndaelManaged)
{
return rijndaelManaged.CreateDecryptor()
.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
}
// Encrypts plaintext using AES 128bit key and a Chain Block Cipher and returns a base64 encoded string
public String Encrypt(String plainText, String key)
{
var plainBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(Encrypt(plainBytes, GetRijndaelManaged(key)));
}
public String Decrypt(String encryptedText, String key)
{
var encryptedBytes = Convert.FromBase64String(encryptedText);
return Encoding.UTF8.GetString(Decrypt(encryptedBytes, GetRijndaelManaged(key)));
}
Java implementation:
private final String characterEncoding = "UTF-8";
private final String cipherTransformation = "AES/CBC/PKCS5Padding";
private final String aesEncryptionAlgorithm = "AES";
public byte[] decrypt(byte[] cipherText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpecy = new SecretKeySpec(key, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec);
cipherText = cipher.doFinal(cipherText);
return cipherText;
}
public byte[] encrypt(byte[] plainText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
plainText = cipher.doFinal(plainText);
return plainText;
}
private byte[] getKeyBytes(String key) throws UnsupportedEncodingException{
byte[] keyBytes= new byte[16];
byte[] parameterKeyBytes= key.getBytes(characterEncoding);
System.arraycopy(parameterKeyBytes, 0, keyBytes, 0, Math.min(parameterKeyBytes.length, keyBytes.length));
return keyBytes;
}
public String encrypt(String plainText, String key) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
byte[] plainTextbytes = plainText.getBytes(characterEncoding);
byte[] keyBytes = getKeyBytes(key);
return Base64.encodeToString(encrypt(plainTextbytes,keyBytes, keyBytes), Base64.DEFAULT);
}
public String decrypt(String encryptedText, String key) throws KeyException, GeneralSecurityException, GeneralSecurityException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException{
byte[] cipheredBytes = Base64.decode(encryptedText, Base64.DEFAULT);
byte[] keyBytes = getKeyBytes(key);
return new String(decrypt(cipheredBytes, keyBytes, keyBytes), characterEncoding);
}
Your C# translation looks like it's doing the right thing for the most part, because the first block matches. What doesn't match is the last block, and that's because the Java code is zero-padding the last block to fill it out, whereas your C# code doesn't do that, so it'd use PKCS #5 padding by default.
PKCS #5 padding is much better than zero-padding, of course, but since the latter is what the Java code used, you'd have to do the same thing. (That means, call swEncrypt.Write((byte) 0) a few more times until the byte count is a multiple of 16.)
There's yet another subtlety. The Java code translates the string to bytes using String.getBytes(), which uses the "default encoding" of the Java runtime. This means that if your string contains non-ASCII characters, you'd run into interoperability issues. Best practice is to use UTF-8, but seeing as you can't change the Java code, I guess there's not much you can do about that.