PowerShell ConvertFrom-SecureString in Java? - java

I'm trying to re-implement PowerShell's ConvertFrom-SecureString in Java. The documentation there is a bit sparse. All it says is "AES". Trying out various AES encryption examples on the Java side did not get me any further.
Would someone have some pointers for me?
Addendum 1
The pointers provided by the comments look helpful. Thanks!
This PowerShell script shows what I'm trying to "port" to Java, at least the encryption part (with key being just 32 random byte values):
$key = 202,144,73,88,228,1,7,104,95,212,137,87,125,201,80,75,53,18,39,108,60,218,212,151,239,241,34,117,106,184,212,179
$secret = "banana"
Write-Output "secret : $($secret)"
# encrypt
$secretSecured = ConvertTo-SecureString $secret -AsPlainText -Force
$encrypted = ConvertFrom-SecureString -secureString $secretSecured -key $key
Write-Output "encrypted: $($encrypted)"
# decrypt
$secure = $encrypted | ConvertTo-SecureString -key $key
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
$decrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
Write-Output "decrypted: $($decrypted)"
Running it gives this:
secret : banana
encrypted: 76492d1116743f0423413b16050a5345MgB8AEkAdQBpAEIANQBaAHQAeQAvAE0AOQBpAE0ATwByAEEAcgBlAC8AOQB2AGcAPQA9AHwANwBkAGIANwBiADgAYgBhADIAZgA4ADAAZQBlADEAZQA0ADgAYQA1ADUAYgBiADYAZgAyADQAMQA5ADkAMwA5AA==
decrypted: banana
Addendum 2
Here's now a first draft port in Java (also in this Github repository):
import org.junit.Before;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptionTest{
SecretKeySpec key;
IvParameterSpec ivSpec;
Cipher cipher;
#Before
public void init() throws Exception{
int[] ints = {202,144,73,88,228,1,7,104,95,212,137,87,125,201,80,75,53,18,39,108,60,218,212,151,239,241,34,117,106,184,212,179};
byte[] bytes = new byte[ints.length];
for (int i=0; i<ints.length; i++){
bytes[i] = (byte)ints[i];
}
key = new SecretKeySpec(bytes, "AES");
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
ivSpec = new IvParameterSpec(iv);
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
#Test
public void testIt() throws Exception{
String secret = "banana";
System.out.println("secret : "+secret);
byte[] encrypted = encrypt(secret);
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(encrypted));
String decrypted = decrypt(encrypted);
System.out.println("decrypted: "+decrypted);
}
byte[] encrypt(String secret) throws Exception{
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(secret.getBytes());
}
String decrypt(byte[] encrypted) throws Exception{
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return new String(cipher.doFinal(encrypted));
}
}
Running it (in JUnit) gives this:
secret : banana
encrypted: P51DH0d0FRvUoetaujEyjA==
decrypted: banana
I wonder why the encrypted string here is only 24 characters long, but the PowerShell one has 192 characters?

Related

Node JS encryption code to Java encryption code

I'm trying to find the similar java code for the below Node JS code,
Node JS Code:
var crypto = require('crypto');
var mykey = crypto.createCipher('aes-128-ecb', 'XXXXXXXX00000000');
var mystr = mykey.update('HelloWorld', 'utf8', 'hex')
mystr += mykey.final('hex');
console.log(mystr);
Encryption Result: ce25d577457cf8113fa4d9eb16379529
Java Code:
public static String toHex(String arg) throws UnsupportedEncodingException {
return String.format("%x", new BigInteger(1, arg.getBytes("UTF-8")));
}
public static void main(String args[]) throws Exception{
byte[] key = "XXXXXXXX".getBytes();
String message = "HelloWorld";
try {
key = Arrays.copyOf(key, 16);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte encstr[] = cipher.update(message.getBytes());
String encData = new String(encstr, "UTF-8");
encData = toHex(encData);
byte encstr2[] = cipher.doFinal();
String encData2 = new String(encstr2);
encData = encData + toHex(encData2);
System.out.println(encData);
} catch (NoSuchAlgorithmException nsae) {
throw new Exception("Invalid Java Version");
} catch (NoSuchPaddingException nse) {
throw new Exception("Invalid Key");
}
}
Encryption Result: 056efbfbdefbfbd7c7760efbfbdefbfbdefbfbd39262cefbfbdefbfbd5166
Taking up the comments of #Robert and #Topaco I wrote a simple decryption program that is working for the given password 'XXXXXXXX00000000'.
Please keep in mind that this progrsm is using UNSECURE AES ECB mode and UNSECURE MD5 hash algorithm.
The program does NOT provide any proper exception handling.
This is the result:
ciphertext Java: ce25d577457cf8113fa4d9eb16379529
ciphertext NodeJS: ce25d577457cf8113fa4d9eb16379529
My code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MainSo {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/63263081/node-js-encryption-code-to-java-encryption-code");
String plaintext = "HelloWorld";
String keyString = "XXXXXXXX00000000"; // 16 chars
byte[] key = keyString.getBytes(StandardCharsets.UTF_8);
// md5 hashing is unsecure !!
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] keyMd5 = md.digest(key);
// aes ecb mode encryption is unsecure
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(keyMd5, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
System.out.println("ciphertext Java: " + bytesToHex(ciphertext));
System.out.println("ciphertext NodeJS: " + "ce25d577457cf8113fa4d9eb16379529");
}
private static String bytesToHex(byte[] bytes) {
// service method for displaying a byte array as hex string
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}

BadPaddingException just with letters like "o", "b", "c"

I'm making a program which works with messages cryptography by Socket. But, when in my messages has a "o", or "b", or "c" and another letters, i receives that Exception in the decrypto moment.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at teste1.Decrypt.decrypt(Decrypt.java:15)
at teste1.Server.main(Server.java:24)
Yep, my message arrives completed with all the characters, so i don't think in some character was lost in the trasmission. So i don't really know what's the problem, because i've tried to changes a lot of things, but i continued recieving this Exception.
Decrypt class:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Decrypt{
String IV = "AAAAAAAAAAAAAAAA";
public String decrypt(String str, String keys) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(decrypt.doFinal(str.getBytes()),"UTF-8");
}
}
If wants the encrypt class too:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encrypt {
String IV = "AAAAAAAAAAAAAAAA";
public byte[] encrypt(String menE, String keys) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/EBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return encrypt.doFinal(menE.getBytes());
}
}
That happens because Strings change your bytes, you should really use Base64
if strings are a must.
If you want to test that run this code:
byte[] aByte = {-45};
System.out.println(Arrays.toString(new String(aByte, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8)));
It will output: [-17, -65, -67] (which is not -45).
Anyways so a few tips for you:
You cannot encrypt with "ECB" and decrypt with "CBC".
An IV should not be a constant. you should generate a new IV for every message and send it along with the message.
Don't specify "UTF-8" use StandardCharsets.UTF_8 (note if using android: StandardCharsets.UTF-8 is API 19+ so you should have a constant for Charset.forName("UTF-8"))
Here is some example code for how to do it with Base64:
public byte[] encrypt(String message, String key, String iv) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return encrypt.doFinal(/*Get bytes from your message*/message.getBytes(StandardCharsets.UTF_8));
}
public String decrypt(String encryptedMessage, String key, String iv) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
decrypt.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return new String(decrypt.doFinal(Base64.getDecoder().decode(encryptedMessage)), StandardCharsets.UTF_8);
}
And run it with
//your message
String message = "Hello World!";
//generate a new AES key. (an AES key is just a random sequence 16 bytes)
SecureRandom random = new SecureRandom();
byte[] aesKey = new byte[16];
random.nextBytes(aesKey);
//generate a new initialization vector (iv) which is also a random sequence of 16 bytes.
byte[] iv = new byte[16];
random.nextBytes(iv);
String aesKeyAsString = Base64.getEncoder().encodeToString(aesKey);
String ivAsString = Base64.getEncoder().encodeToString(iv);
//encrypt
byte[] encrypted = encrypt(message, aesKeyAsString, ivAsString);
//enocde your encrypted byte[] to String
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
//decrypt
String decrypted = decrypt(encryptedString, aesKeyAsString, ivAsString);
//print your results
System.out.println("Encrypted: " + encryptedString + " Decrypted: " + decrypted);
Outputs:
Encrypted: |encrypted string depended on the generated key and iv| Decrypted: Hello World!
You can also use the more efficient way and use byte[] instead of Strings but it's your choice.

Java openssl encryption / decryption key generation [duplicate]

This question already has answers here:
How to decrypt file in Java encrypted with openssl command using AES?
(4 answers)
AES 256 Encryption Issue
(1 answer)
Closed 7 years ago.
I'm using Java 8 and I'm attempting to emulate the following openssl calls with Java.
Encrypt:
echo -n 'hello world' | openssl enc -a -aes-256-cbc -md sha256 -pass pass:97DE:4F76
U2FsdGVkX18PnO/NLSxJ1pg6OKoLyZApMz7aBRfKhJc=
Decrypt:
echo U2FsdGVkX18PnO/NLSxJ1pg6OKoLyZApMz7aBRfKhJc= | openssl enc -d -a -aes-256-cbc -md sha256 -pass pass:97DE:4F76
hello world
Questions:
My implementation doesn't work. I've visited many other StackOverflow answers but wasn't able to figure out the right implementation. Can anyone point me out in the right direction for solving this?
The openssl system call in the example above uses digest sha256. If I were to use sha1, instead in the Java implemementation, would it be just a matter of changing PBKDF2WithHmacSHA256 with PBKDF2WithHmacSHA1?
Test.java
package test;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class Test {
public static final String PASSWORD = "97DE:4F76";
public static String encryptString(String clearText, String password) {
return "";
}
// echo U2FsdGVkX18PnO/NLSxJ1pg6OKoLyZApMz7aBRfKhJc= | openssl enc -d -a -aes-256-cbc -md sha256 -pass pass:97DE:4F76
//
// see https://stackoverflow.com/a/992413, https://stackoverflow.com/a/15595200,
// https://stackoverflow.com/a/22445878, https://stackoverflow.com/a/11786924
public static String decryptString(String cypherText, String password) {
byte[] dataBase64 = DatatypeConverter.parseBase64Binary(cypherText);
byte[] salt = {
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0
};
try {
// generate the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // "PBKDF2WithHmacSHA1"
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// decrypt the message
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(dataBase64);
String answer = new String(decrypted, "UTF-8");
return answer;
} catch (Exception ex) {
ex.printStackTrace();
}
return "";
}
public static void main(String[] args) {
System.out.println(decryptString("U2FsdGVkX18PnO/NLSxJ1pg6OKoLyZApMz7aBRfKhJc=", PASSWORD));
}
}
this is the current output of running the code above:
java.security.InvalidKeyException: Illegal key size at
javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1039) at
javax.crypto.Cipher.init(Cipher.java:1393) at
javax.crypto.Cipher.init(Cipher.java:1327) at
test.Test.decryptString(Test.java:42) at
test.Test.main(Test.java:55)
Update:
this is the code I ended up implementing after I used this answer: https://stackoverflow.com/a/11786924 -> has the rest of the constants and implementation of EVP_BytesToKey
public static String decryptString(String cypherText, String password) {
try {
// decode the base64 cypherText into salt and encryptedString
byte[] dataBase64 = DatatypeConverter.parseBase64Binary(cypherText);
byte[] salt = Arrays.copyOfRange(dataBase64, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
byte[] encrypted = Arrays.copyOfRange(dataBase64, CIPHERTEXT_OFFSET, dataBase64.length);
System.out.println("dataBase64 = " + new String(dataBase64));
System.out.println("salt: " + new BigInteger(1, salt).toString(16));
System.out.println("encrypted: " + new BigInteger(1, encrypted).toString(16));
// --- specify cipher and digest for EVP_BytesToKey method ---
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
// create key and IV
final byte[][] keyAndIV = EVP_BytesToKey(
KEY_SIZE_BITS / Byte.SIZE,
cipher.getBlockSize(),
sha1,
salt,
password.getBytes("ASCII"),
ITERATIONS);
SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
// initialize the Encryption Mode
cipher.init(Cipher.DECRYPT_MODE, key, iv);
// decrypt the message
byte[] decrypted = cipher.doFinal(encrypted);
String answer = new String(decrypted, "UTF-8"); // should this be "ASCII"?
return answer;
} catch (Exception ex) {
ex.printStackTrace();
}
return "";
}

AES-128 Encrypted String not properly padded

I'm having trouble creating an encrypted string using AES/CBC/PKCS5Padding with a 128-bit key. I have code to decrypt an encrypted string. I have an example encrypted string from another system that decrypts successfully, but when I try to create my own encrypted string it is not padded properly for some reason. When I decrypt my encrypted string it only shows the characters after the 16 byte.
All of the examples I find either assume the encryption happens first then decryption happens right after that with variables set during encryption or they are randomly generating a key, but in my case i want to use a known key.
I am really stuck so any help would be greatly appreciated, thank you very much for your time and efforts!
Example:
Original Text: 01234567891234565
Encrypted: zmb16qyYrdoW6akBdcJv7DXCzlw0qU7A2ea5q4YQWUo=
Key length: 16
Decrypted: 5 (this is the last digit in the Original Text String)
Sample Code:
package com.company.encrypt.tests;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class TestEncryptDecrypt {
private static final String characterEncoding = "UTF-8";
private static final String cipherTransformation = "AES/CBC/PKCS5Padding";
private static final String aesEncryptionAlgorithm = "AES";
public static void main(String[] args) throws Exception {
String key1 = "1234567812345678";
String text = "01234567891234565";
System.out.println("Original Text: " + text);
String encrypted = encrypt(text, key1);
System.out.println("Encrypted: " + encrypted);
String decrypted = decrypt(encrypted, key1);
System.out.println("Decrypted: " + decrypted);
}
public static String decrypt(String encryptedText, String key) throws Exception {
String plainText = null;
int keyLength = key.length();
System.out.println("Key length: " + String.valueOf(keyLength));
byte[] encryptedTextBytes = Base64.decodeBase64(encryptedText.getBytes());
byte[] keyBytes = key.getBytes();
byte[] initialVector = Arrays.copyOfRange(encryptedTextBytes, 0, keyLength);
byte[] trimmedCipherText = Arrays.copyOfRange(encryptedTextBytes, keyLength, encryptedTextBytes.length);
try {
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] clearText;
clearText = cipher.doFinal(trimmedCipherText);
plainText = new String(clearText, characterEncoding);
} catch(NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException
| InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return plainText;
}
public static String encrypt(String plainText, String encryptionKey) throws Exception {
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), aesEncryptionAlgorithm);
Cipher cipher = Cipher.getInstance(cipherTransformation);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainTextBytes = plainText.getBytes("UTF-8");
byte[] encrypted = cipher.doFinal(plainTextBytes);
return new String(Base64.encodeBase64(encrypted));
}
}
I've noticed that in the decrypt() function, you separated the encrypted array into two parts: first 16 bytes, and the rest. You used the first 16 bytes as the IV for decryption, however, you did not prepend the 16 byte IV to the beginning of the encrypted message in encrypt(). This results in the first 16 bytes of the plaintext to be lost. I presume you assumed that doFinal() automatically does that for you, but it doesn't.
To fix this, before returning the encrypted message, prepend the IV, which can be retrieved using cipher.getIV(). You can accomplish this using the ArrayUtils.addAll() from Apache Commons Lang library, or simply write your own function to do it. Another thing to note is that the IV will always be the block size, which is 16 bytes for AES, no matter the key size.
Hope this answer helps!

Encryption and Decryption in Java using AES

How to Encrypt and Decrypt in Java using AES
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class Encryption {
public static void main(String args[]) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
String t = "Testing";
byte[] dataToSend = t.getBytes();
byte[] key = new byte[16];
Cipher c = Cipher.getInstance("AES");
SecretKeySpec k = new SecretKeySpec(key, "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal(dataToSend);
System.out.println(encryptedData);
byte[] key2 = new byte[16];
byte[] encryptedData2 = encryptedData;
Cipher c2 = Cipher.getInstance("AES");
SecretKeySpec k2 =
new SecretKeySpec(key2, "AES");
c2.init(Cipher.DECRYPT_MODE, k2);
byte[] data = c.doFinal(encryptedData);
System.out.println(data);
}
}
Since the problem is not specified explicitly, I am pointing out the obvious errors with the presumption that 'Testing' encrypted should print 'Testing' once decrypted.
AES is a symmetric key algorithm, hence you have to use the same key for encrypting and decrypting (k2 should not be there at all)
c.doFinal of the encrypted data doesn't make much sense, this should be reference to your Cipher DECRYPT_MODE -> c2
Hence, change:
c2.init(Cipher.DECRYPT_MODE, k2);
To:
c2.init(Cipher.DECRYPT_MODE, k);
And change:
byte[] data = c.doFinal(encryptedData);
To:
byte[] data = c2.doFinal(encryptedData);
To print the final outcome:
Change:
System.out.println(data);
To:
System.out.println(new String(data));

Categories