java encrypted string cannot be stored in DB - java

Trying to use the javax.crypto library to encrypt a string and store it in the database (Oracle). I will need to decrypt this string later, so I need a two-way algorithm.
The problem is the database doesn't seem to accept some of the encrypted characters the method creates. We are in between migrating our databases to a new server. The old databases use US7ASCII charset while the new databases use AL32UTF8. When I go to put the encrypted string in the database, the database just converts them to question marks (?) in the US7ASCII databases. It appears to store just fine in the AL32UTF8 database.
So, I have to make this cross-compatible. I've tried sending it different StandardCharsets values when using the getBytes() method, but it doesn't seem to help. Maybe I'm missing something. Any way I can get the desired result doing it differently?
Here is my code to generate the cipher text. Modded from another post on StackOverflow
import java.io.PrintStream;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class test
{
public static void main(String[] args)
throws Exception
{
//byte[] encryptionKey = "Es6XYPkgCV75J95Y".getBytes(StandardCharsets.UTF_8);
byte[] encryptionKey = "Es6XYPkgCV75J95Y".getBytes(StandardCharsets.ISO_8859_1);
//byte[] plainText = args[0].getBytes(StandardCharsets.UTF_8);
byte[] plainText = args[0].getBytes(StandardCharsets.ISO_8859_1);
MyCrypto aes = new MyCrypto(encryptionKey);
byte[] cipherText = aes.encrypt(plainText);
byte[] decryptedCipherText = aes.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
}
}
class MyCrypto
{
private byte[] key;
private static final String ALGORITHM = "AES";
public MyCrypto(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* #param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* #param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}

When you encrypt data you are turning it into binary data. It sounds like you are trying to store it as character data, which is a bad idea.
You really have two options;
Encode the encrypted binary data using a binary-to-text scheme such as Base64. You can then store the encoded data as character data. When you want to retrieve it you read the character data and decode (text to binary) before decrypting.
Store the encrypted binary data as binary (for example as a BLOB).

Related

Safely transport SecretKey for encryption/decryption of Password

I am thinking about using ChaCha20-Poly1305 to encrypt/decrypt a password (I need to encrypt/decrypt cannot only hash). But, to use this algorithm we are required to use a Secret Key. However, this is a problem because I am encrypting/decrypting the user's password on their device, so in my DB I just store their encrypted password.
The problem with it, is if the user uninstall my app from their phone or change to a new phone, I would need the same secret key to be able to decrypt the user's password again.
My question is: How do I safely transfer and store this secret key ?
Also, please let me know if you have better alternatives for my problem, such as different encryption algorithm or methods.
Here is a code example with the SecretKey object that I've mentioned:
package com.javainterviewpoint;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ChaCha20Poly1305Example
{
static String plainText = "This is a plain text which will be encrypted by ChaCha20 Poly1305 Algorithm";
public static void main(String[] args) throws Exception
{
KeyGenerator keyGenerator = KeyGenerator.getInstance("ChaCha20");
keyGenerator.init(256);
// Generate Key
SecretKey key = keyGenerator.generateKey();
System.out.println("Original Text : " + plainText);
byte[] cipherText = encrypt(plainText.getBytes(), key);
System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText));
String decryptedText = decrypt(cipherText, key);
System.out.println("DeCrypted Text : " + decryptedText);
}
public static byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception
{
byte[] nonceBytes = new byte[12];
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// Create IvParamterSpec
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, SecretKey key) throws Exception
{
byte[] nonceBytes = new byte[12];
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// Create IvParamterSpec
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
}
For symmetric encryption and decryption, a best practice is use a MASTER KEY which is fixed string or has a fixed algorithm which just you know how to generate it.
You must use this MASTER KEY for encryption and decryption all other keys (name WORKING KEYS) to encrypt/decrypt real data.
If your device has a secure area which you can save your keys safely, you must inject master key on it by another device or by communicating with a server. If it has not, you are in RISK for disclosing keys.
However, if you are the only programmer of the app, you can use a fixed algorithm to generate MASTER key. For example hash(xor( of 1c1c1c with the ID of the device. Now, the device can send a request for getting working keys (by sending its id and mac-block which is encrypted by master key) to a server and you give back the right working keys.
This scenario use in payments by ISO-8583.

Java: SecretKey to String and rebuilding back to SecretKey produces different decryption result

Currently i've created a SecretKey for use in a RC4 encryption for my assignment. After the RC4 encryption i would then convert this key into string and send it to a server via UDP however when i rebuild it on the server side using SecretKeySpec, it would produce a completely different secret key.
I've looked around stackoverflow for solutions but the end would still result in a rebuilt SecretKey being different from my original SecretKey.
I've tried rebuilding the Secret Key from the String format on the client code and the result would still be a different SecretKey compared to the original so i doubt my UDP tranmission has anything to do with the result.
Below are how i went about creating the initial SecretKey for use in a RC4 encryption:
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
How i converted the SecretKey to String and rebuilt using SecretKeySpec:
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
When I println "originalSecretKey" and "rebuiltSK" for checking, this is where I realised the rebuilt values are completely different and therefore i wouldnt be able to decrypt any originalSecretKey-encrypted message using the rebuiltSK.
Edit1: Silly me, thank you to "A Developer" and "Daniel" for pointing out that the actual .getEncoded() values of "originalSecretKey" and "rebuiltSK" are the same.
Apologies if I'm missing something extremely basic regarding Key generation and java's cryptography as this is my first time using them. Thank you in advance for your help !
Edit2:
Below is the code i'm currently using for my RC4 encryption and decryption:
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
//Cipher.DECRYPT_MODE when on server program
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
The code above is the reason why I'm trying to rebuild the secretKey on the server end after receiving it in byte[] from the client.
I've tried running the decryption with the "rebuiltSK" as the SecretKey argument however it doesn't produce the correct plaintext although I've checked the packet.getData() on both client and server to be the same.
Your rebuilding of the SecretKey works like expected and the encryption followed by the decryption retrieves the
original plaintext.
I can only that argue (same as #Daniel) that the key was changed during transmission or the (byte[] with the)
ciphertext was not fully transmitted to the server.
The below full example code shows a complete round with key generation, encryption and decryption.
This is the result:
plaintext equals decryptedtext: true
decryptedtext: my secret
Security warning: The code below uses an UNSECURE algorithm 'RC4' or 'ARCFOUR'.
Please do not copy below code or use it in production - it is for educasional purposes only.
The code does not have any proper exception handling !
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("https://stackoverflow.com/questions/63185927/java-secretkey-to-string-and-rebuilding-back-to-secretkey-produces-different-de");
// security warning: the algorithm 'RC4' or 'ARCFOUR' is unsecure and
// should be used for educational purposes only
// do not use this code in production
// key generation
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
// encryption
byte[] plaintext = "my secret".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = encryptRC4(plaintext, originalSecretKey);
// decryption
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
byte[] decryptedtext = decryptRC4(ciphertext, rebuiltSK);
// output
System.out.println("plaintext equals decryptedtext: " + Arrays.equals(plaintext, decryptedtext));
System.out.println("decryptedtext: " + new String(decryptedtext));
}
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
public static byte[] decryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, k);
byte[] decrypted = cipher.doFinal(b);
return decrypted;
}
}

Java DES/ECB/PKCS5Padding encrypted value are not same

I am trying to encrypt same data in Java using DES/ECB/PKCS5Padding algorithm with hardcoded secret key. I verifed my encrypted value using online tool. If i used secret key with special characters then Java and online tool encrypted value are not identical.
If i used Key 2b7e151628aed2a6abf71589 and input text 1234 then encrypted result is same with online tool. Encrypted result for this text using Java is SRC/0ptoT78= which is same with online tool, Image is also attached for reference
but If i used Key /£½ZBÝy‚÷Í( Ó—&*Ænù­;‘³ and again input text 1234 then encrypted result is not same with online tool. Encrypted result for this text using Java is UUoh48bB9dM= which is not same with online tool, Image is also attached for reference
My java code is as below
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.Key;
public class Main {
public static void main(String[] args) {
try {
System.out.println(encrypt("/£½ZBÝy‚÷Í( Ó—&*Ænù­;‘³", "1234"));
System.out.println(encrypt("2b7e151628aed2a6abf71589", "1234"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static String encrypt(String key, String str) throws Exception {
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
System.out.println(new String(key.getBytes(), "UTF-8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
Key secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] bytes = cipher.doFinal(str.getBytes("utf-8"));
byte[] base64Bytes = Base64.encodeBase64(bytes);
return new String(base64Bytes);
}
public static String decrypt(String key, String str) throws Exception {
byte[] data =Base64.decodeBase64(str);
DESKeySpec dks = new DESKeySpec(key.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
Key secretKey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(data);
return new String(decryptedBytes, "gb2312");
}
}
This is very likely a conflict between your local default encoding and the website's default encoding.
To solve it, specify an encoding when you extract the bytes of your key:
import static java.nio.charsets.StandardCharsets.UTF_8;
...
DESKeySpec dks = new DESKeySpec(key.getBytes(UTF_8))
Also, you specify a non-standard encoding, gb2312. Try using UTF-8, UTF-16 or friends instead.
return new String(decryptedBytes, UTF_8);
If it still doesn't work, try using the other values available in StandardCharsets (see Javadoc).

Problem with java Cipher when i want to decipher it

I have made an app with javafx that I can write something and save it to database. my database is sqlite. it's a very simple app. although I've added login app to my writing app, still the sqlite can be opened by any software.
instead of encrypting the sqlite db(which i didn't know and i found really confusing to do :) ) I decided to encrypt the text in java and later when i want to read it i would turn it back to normal and show it.
I learned how to do it from this link and i changed it to print the string instead of writing to a file
so my final code looks like this:
public static void main(String[] args) throws Exception {
String textA = "";
String textB="";
byte[] thisismykey = "Hello How manyBytes are in#hts A".getBytes();
SecretKey secKey = new SecretKeySpec(thisismykey, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
//turn your original text to byte
byte[] myoriginaltexttobyte = "Your Plain Text Here".getBytes();
//activate the encrypt method
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
//encrypt the text and assign the encrypted text to a byte array
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
//change it to string with new string
textA = new String(bytecipheredoforgtext);
System.out.println(textA);
//get the bytes of encrypted text and assign it to a byte array
byte[] byteofencryptedtext = textA.getBytes();
//activate the decrypt mode of the cipher
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
//decrypt the encrypted text and assign it to a byte array
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
//change it to string with new string
textB = new String(byteofencryptedbacktonormaltext);
System.out.println(textB);
}
now that encrypt and decrypt are at the same method it works perfectly but I want to change it to a class with different methods so i could encrypt a text with one method and decrypt it with another. but when i separate things decrypt doesn't work well. Encrypt work well. this is the code now:
public class CipherFinalB {
//from https://stackoverflow.com/questions/20796042/aes-encryption-and-decryption-with-java/20796446#20796446
private final byte[] thisismykey;
private final SecretKey secKey;
private final Cipher aesCipher;
public CipherFinalB() throws NoSuchPaddingException, NoSuchAlgorithmException {
thisismykey = "HellodHowfmanyBytesgarehin#htseA".getBytes();
secKey = new SecretKeySpec(thisismykey, "AES");
aesCipher = Cipher.getInstance("AES");
}public String encrypt (String originaltext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] myoriginaltexttobyte =originaltext.getBytes();
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
String textA = new String(bytecipheredoforgtext);
System.out.println(textA);
return new String(bytecipheredoforgtext);
}
public String decrypt (String encryptedtext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] byteofencryptedtext = encryptedtext.getBytes();
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
return new String(byteofencryptedbacktonormaltext);
}
}
when i use the encrypt method it gives me back a string. and when i send the same string to decrypt method it doesn't work and gives me the following error:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:936)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2191)
at main.CipherFinalB.decrypt(CipherFinalB.java:66)
at main.CipherTest.main(CipherTest.java:16)
What should i do?
UPDATE ANSWER:
as #Juan said the problem was "when data is ciphered you may have any byte in the array, not only printable characters." So I changed the method to return byte for encrypt method and decrypt method. decrypt method now gets byte as parameter instead of string and now everything works fine.
the updated code looks like this:
public byte[] encrypt (String originaltext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] myoriginaltexttobyte =originaltext.getBytes();
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
String textA = new String(bytecipheredoforgtext);
System.out.println(textA);
return bytecipheredoforgtext;
}
public byte[] decrypt (byte[] encryptedtext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] byteofencryptedtext = encryptedtext;
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
return byteofencryptedbacktonormaltext;
}
I am not sure that the error is caused by this but the conversion between String and byte[] you are doing may work with bytes in the character set range, but when data is ciphered you may have any byte in the array, not only printable characters.
After you get the byte[] with the cyphered text, encode it as Base64 and store the string representation.
When decrypting, first decode the Base64 into the byte[] and then decipher it.
See Base64 class

AES-256-CTR Encryption in node JS and decryption in Java

I am trying to encode in nodejs and decryption for the same in nodejs works well. But when I try to do the decryption in Java using the same IV and secret, it doesn't behave as expected.
Here is the code snippet:
Encryption in nodeJs:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
_ = require('lodash'),
secret = 'd6F3231q7d1942874322a#123nab#392';
function encrypt(text, secret) {
var iv = crypto.randomBytes(16);
console.log(iv);
var cipher = crypto.createCipheriv(algorithm, new Buffer(secret),
iv);
var encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
var encrypted = encrypt("8123497494", secret);
console.log(encrypted);
And the output is:
<Buffer 94 fa a4 f4 a1 3c bf f6 d7 90 18 3f 3b db 3f b9>
94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e
Code Snippet for decryption in JAVA:
public class Test {
public static void main(String[] args) throws Exception {
String s =
"94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e";
String seed = "d6F3231q7d1942874322a#123nab#392";
decrypt(s, seed);
}
private static void decrypt(String s, String seed)
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String parts[] = s.split(":");
String ivString = parts[0];
String encodedString = parts[1];
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] secretBytes = seed.getBytes("UTF-8");
IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivString));
/*Removed after the accepted answer
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);*/
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
byte[] output = cipher.doFinal(hexStringToByteArray(encodedString));
System.out.println(new String(output));
}
}
Output: �s˸8ƍ�
I am getting some junk value in the response. Tried a lot of options, but none of them seem to be working. Any lead/help is appreciated.
In your JS code, you're using the 32-character string d6F3231q7d19428743234#123nab#234 directly as the AES key, with each ASCII character directly mapped to a single key byte.
In the Java code, you're instead first hashing the same string with MD5, and then using the MD5 output as the AES key. It's no wonder that they won't match.
What you probably should be doing, in both cases, is either:
randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
using a key derivation function (KDF) to take an arbitrary input string and turn it into a pseudorandom AES key.
In the latter case, if the input string is likely to have less than 256 bits of entropy (e.g. if it's a user-chosen password, most of which only have a few dozen bits of entropy at best), then you should make sure to use a KDF that implements key stretching to slow down brute force guessing attacks.
Ps. To address the comments below, MD5 outputs a 16-byte digest, which will yield an AES-128 key when used as an AES SecretKeySpec. To use AES-256 in Java, you will need to provide a 32-byte key. If trying to use a 32-byte AES key in Java throws an InvalidKeyException, you are probably using an old version of Java with a limited crypto policy that does not allow encryption keys longer than 128 bits. As described this answer to the linked question, you will either need to upgrade to Java 8 update 161 or later, or obtain and install an unlimited crypto policy file for your Java version.
In the Java code you are taking the MD5 hash of secret before using it as a key:
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Whereas, in your NodeJS code, you don't do this anywhere. So you're using two different keys when encrypting and decrypting.
Don't copy and paste code without understanding it. Especially crypto code.
Faced with the same task (but with 128, it easy to adapt for 256), here is working Java/NodeJs code with comments.
It's additionally wrapped to Base64 to readability, but it's easy to remove if you would like.
Java side (encrypt/decrypt) :
import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
// one class needs to have a main() method
public class MyClass
{
private static void log(String s)
{
System.out.print("\r\n"+s);
}
public static SecureRandom IVGenerator() {
return new SecureRandom();
}
// arguments are passed using the text field below this editor
public static void main(String[] args)
{
String valueToEncrypt = "hello, stackoverflow!";
String key = "3e$C!F)H#McQfTjK";
String encrypted = "";
String decrypted = "";
//ENCODE part
SecureRandom IVGenerator = IVGenerator();
byte[] encryptionKeyRaw = key.getBytes();
//aes-128=16bit IV block size
int ivLength=16;
byte[] iv = new byte[ivLength];
//generate random vector
IVGenerator.nextBytes(iv);
try {
Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));
//encrypt
byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());
ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);
//storing IV in first part of whole message
byteBuffer.put(iv);
//store encrypted bytes
byteBuffer.put(cipherText);
//concat it to result message
byte[] cipherMessage = byteBuffer.array();
//and encrypt to base64 to get readable value
encrypted = new String(Base64.getEncoder().encode(cipherMessage));
} catch (Exception e) {
throw new IllegalStateException(e);
}
//END OF ENCODE CODE
log("encrypted and saved as Base64 : "+encrypted);
///DECRYPT CODE :
try {
//decoding from base64
byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
//retrieving IV from message
iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);
//retrieving encrypted value from end of message
byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);
Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");
decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
//decrypt
byte[] finalCipherText = decryptionCipher.doFinal(cipherText);
//converting to string
String finalDecryptedValue = new String(finalCipherText);
decrypted = finalDecryptedValue;
} catch (Exception e) {
throw new IllegalStateException(e);
}
log("decrypted from Base64->aes128 : "+decrypted);
//END OF DECRYPT CODE
}
}
It could be easy be tested by online java compilers (this example prepared on https://www.jdoodle.com/online-java-compiler).
NodeJs decrypt side :
const crypto = require('crypto');
const ivLength = 16;
const algorithm = 'aes-128-ctr';
const encrypt = (value, key) => {
//not implemented, but it could be done easy if you will see to decrypt
return value;
};
function decrypt(value, key) {
//from base64 to byteArray
let decodedAsBase64Value = Buffer.from(value, 'base64');
let decodedAsBase64Key = Buffer.from(key);
//get IV from message
let ivArr = decodedAsBase64Value.slice(0, ivLength);
//get crypted message from second part of message
let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);
let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);
//decrypted value
let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');
decrypted += cipher.final('utf8');
return decrypted;
}

Categories