I want to encrypt a string in Java and decrypt it Javascript. I tried Crypto in javascript, but decrypted incorrectly. What is the simplest way to decrypt in Javascript?
I used the encryption code from the following link:
http://bryox.blogspot.in/2011/12/encrypt-and-decrypt-string-by-java.html
What is the simplest way to decrypt in Javascript?
Insecurely.
Alternatively, if you're looking for the simplest way to securely encrypt/decrypt in Java and Javascript, you might want to take a look at libsodium, which has bindings for Java and JavaScript.
Encrypting in Java with LazySodium
LazySodiumJava lazySodium = new LazySodiumJava(new SodiumJava());
SecretBox.Lazy secretBoxLazy = (SecretBox.Lazy) lazySodium;
Key key = lazySodium.cryptoSecretBoxKeygen();
String msg = "This message needs top security";
byte[] nonce = lazySodium.nonce(SecretBox.NONCEBYTES);
lazySodium.cryptoSecretBoxEasy(msg, nonce, key);
Decrypting in JavaScript with Sodium-Plus
const {SodiumPlus, CryptographyKey} = require('sodium-plus');
let sodium;
async function decryptMessage(ciphertextHex, nonceHex, keyHex) {
if (!sodium) sodium = await SodiumPlus.auto();
let ciphertext = Buffer.from(ciphertextHex, 'hex');
let nonce = Buffer.from(nonceHex, 'hex');
let key = CryptographyKey.from(keyHex, 'hex');
return sodium.crypto_secretbox_open(ciphertext, nonce, key);
}
decryptMessage(ciphertext, nonce, key).then((plaintext) => {
console.log(plaintext.toString());
});
Related
My application uses salted hash in Java. First a random salt is generated. Then this salt is prefixed to the SHA-512 of the input password and the combined string is SHA-512 again.It is implemented as follows:-
String password = testpwd.getText().toString();
SecureRandom rand = new SecureRandom();
byte[] randbytes = new byte[16];
rand.nextBytes(randbytes);
String encodedSalt = Base64.encodeToString(randbytes, Base64.DEFAULT);
MessageDigest digest = MessageDigest.getInstance("SHA-512");
digest.reset();
digest.update(password.getBytes("utf-8"));
byte[] pwdhash = digest.digest();
String encodedPwd = Base64.encodeToString(pwdhash, Base64.DEFAULT);
String saltedPassword = encodedSalt+encodedPwd ;
digest.reset();
digest.update(saltedPassword.getBytes("utf-8"));
byte[] pwdhash1 = digest.digest();
String encodedPwdSalt = Base64.encodeToString(pwdhash1, Base64.DEFAULT);
Then the strings encodedSalt and encodedPwdSalt are sent to web server for authentication. The PHP encryption is as follows:
$postpassword = $_POST['password'];
$postsalt = $_POST['salt'];
$salt = base64_decode($postsalt);
$password = base64_decode('postpassword');
The SHA-512 hashs of the password "Ditglt#785" is stored in the database. It is retrieved and processed as follows:-
$getsaltpwd = $salt.$dbpassword ;
$dbsaltpwd = hash('sha512', $getsaltpwd);
if($dbpassword == $postpassword) {}
The condition always fails and so does the authentication. What should I do ?
The PHP version hashes raw bytes while the Java version hashes base64-encoded strings.
Here's a Java version that matches what your PHP code does:
digest.reset();
digest.update(randbytes);
digest.update(pwdhash);
byte[] pwdhash1 = digest.digest();
String encodedPwdSalt = Base64.encodeToString(pwdhash1, Base64.DEFAULT);
Having said that, it would be more secure to store the salt and the salted password in the database, and to use at least some key derivation function (iterate the hash function many times) in order to counteract any potential brute-forcing of the stored hashes.
Since your Java code correctly follows what you describe in your specification, the problem lies on the PHP side.
With your Java code as-is, it generates the following values when encoding the string "password" with a random salt:
encodedSalt: ww0g+f77ygKD7Iww1GTYtg==
encodedPwd: sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==
encodedPwdSalt: YAGG7GcpUxIZzBnHuaezPf5BWFhFalBPgvue/0wFoRLu+JsKslG8wPCv6dPubIBk1aFIJ8spK8S17347aDBAYA==
In PHP, what you would need to do is the following:
$postpassword = 'YAGG7GcpUxIZzBnHuaezPf5BWFhFalBPgvue/0wFoRLu+JsKslG8wPCv6dPubIBk1aFIJ8spK8S17347aDBAYA==';
$postsalt = 'ww0g+f77ygKD7Iww1GTYtg==';
$dbpassword = 'sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==';
if($postpassword == base64_encode(hash('sha512', $postsalt.$dbpassword, true))) {
echo 'OK';
}
Check if the padding matches. I experienced the same problem with encryption where the padding in PHP was different from the padding in JAVA. Luckily I was able to set the padding in JAVA to the one that PHP uses. But I had to look at PHP source code to figure out how. As far as I remember it was not possible to change the padding in PHP back then.
Here is the question I posted back then: decrypting php encrypted data on android
[...] you need to set Base64.decode with the parameter Base64.NO_WRAPas PHP will just put out the base64 delimited by \0.
I am encrypting a file in Java and need to decrypt it at client side.
This is the server side code:
Key secretKey = new SecretKeySpec("mysecretmysecret".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] outputBytes = cipher.doFinal(read(sampleFile));
return outputBytes;
At client side I use Ajax request to fetch the file and use CryptoJS AES:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'file', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
var encryptedData = this.response;
var decrypted = CryptoJS.AES.decrypt(encryptedData, "mysecretmysecret");
console.log(decrypted);
};
xhr.send();
But this does not decrypt the file. I get this printed as value of decrypted in the console:
W…y.init {words: Array[0], sigBytes: 0}
I have also tried converting arraybuffer to WordArray suggested here but still the same result.
I would be more than glad if someone could point me in the right direction and tell me what I did wrong.
Edit 1:
I have solved the issue. The code I used is posted as an answer.
So I have finally solved this. Thanks to Artjom for pointing in the right direction.
I have changed my Java code to use CBC with PKCS5Padding .
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec("mysecretmysecret".getBytes(), "AES");
IvParameterSpec IVKey = new IvParameterSpec("mysecretmysecret".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, myKey, IVKey);
byte[] outputBytes = cipher.doFinal(read(sampleFile));
return outputBytes;
And my javascript goes like this:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'file', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var encryptedData = this.response;
var passwordWords = CryptoJS.enc.Utf8.parse("mysecretmysecret"); //yes I know, not a big secret!
var wordArray = CryptoJS.lib.WordArray.create(encryptedData);
var decrypted = CryptoJS.AES.decrypt({
ciphertext: wordArray
}, passwordWords, {
iv: passwordWords, //yes I used password as iv too. Dont mind.
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypted); //Eureka!!
};
xhr.send();
The decrypted is a WordArray.
Let's recap, in Java you're using
AES,
ECB (not specified but most often the default; it's insecure!),
PKCS#7 padding (not specified but most often the default; it's the same as PKCS#5 padding), and
a password of 16 characters as a key of 16 bytes (depending on the default system encoding).
If the key is passed as a string to CryptoJS, it will have to derive the key from the assumed password using OpenSSL's EVP_BytesToKey with a single round of MD5. Since your ciphertext is not encoded in an OpenSSL-compatible format, it will fail. The thing is, you don't need that.
The following code would decrypt the ciphertext that is coming from Java correctly, but it's not very secure:
var passwordWords = CryptoJS.enc.Utf8.parse("mysecretmysecret");
var decrypted = CryptoJS.AES.decrypt({
ciphertext: CryptoJS.lib.WordArray.create(encryptedData) // or use some encoding
}, passwordWords, {
mode: CryptoJS.mode.ECB
});
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // in case the plaintext is text
The ECB mode is not included in the basic rollup, so you will have to include that JavaScript file in your page after the main CryptoJS file.
Also, CryptoJS doesn't handle an ArrayBuffer by default. You need to include the shim for that (source: this answer).
The problem with this is its insecurity. ECB mode is very insecure.
Never use ECB mode. It's deterministic and therefore not semantically secure. You should at the very least use a randomized mode like CBC or CTR. The IV/nonce is not secret, so you can send it along with the ciphertext. A common way is to put it in front of the ciphertext.
It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.
Keys can be derived from passwords, but a proper scheme such as PBKDF2 should be used. Java and CryptoJS both support these.
I'm trying to decrypt text in java that is encrypted using CryptoJS. I've read on other posts that they use different default modes and padding so I set them both(java/cryptojs) both to use aes/cbc/nopadding. I no longer get an exception in java, but I am getting a garbled output during decryption
Encryption(JS):
var parsedLogin = JSON.parse(login);
var publicKey = "abcdefghijklmnio";
var publiciv = "abcdefghijklmnio";
var key = CryptoJS.enc.Hex.parse(publicKey);
var iv = CryptoJS.enc.Hex.parse(publiciv);
var encrypted = CryptoJS.AES.encrypt(parsedLogin.password, publicKey, {iv: publiciv}, { padding: CryptoJS.pad.NoPadding, mode: CryptoJS.mode.CBC});
// send encrypted to POST request
DECRYPT (Java)
String PUBLIC_KEY = "abcdefghijklmnio";
String PUBLIC_IV = "abcdefghijklmnio";
byte[] byteArr = PUBLIC_KEY.getBytes();
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
final SecretKeySpec secretKey = new SecretKeySpec(byteArr, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(PUBLIC_IV.getBytes()));
byte[] parsed = Base64.decodeBase64(encrypted.getBytes());
//byte[] parsed = DatatypeConverter.parseBase64Binary(encrypted);
byte[] fin = cipher.doFinal(parsed);
String decryptedString = new String(fin);
The result that I'm getting is like this: Š²Û!aå{’`#"Ûîñ?Œr˜krÆ
I have already tried changing the CHARSET in the getBytes() to US-ASCII, UTF-8 and UTF-16 but all this does is change the garbled text
I have also tried using othe blocking modes and paddings but they failed at the js level. I just need a simple encryption method right now.
NOTE:
Ignore the security issues...like having the key exposed in js, etc. I'll be handling those later..
You shouldn't be able to use AES CBC without padding unless the password is always 16 bytes. It probably applies some sort of default padding that may or may not be a good idea.
Anyway: you need to pass your key and iv to CryptoJS as a WordArray; if you give it a string it will assume you're giving it a passphrase and derive a different key from that. As such, your Java decryption code will be using a different key/iv pair. You can create a WordArray from your strings using
var key = CryptoJS.enc.Utf8.parse("abcdefghijklmnio")
var iv = ...
I was asked to encrypt some text from client side ( web ) before sending it to server side ( java )
So i try to use CryptoJS library for client side.
I encrypt it like this :
var key = "aaaaaaaaaaaaaaaaaaaaaaaa";
var value = "KF169841";
var encryptedString = CryptoJS.TripleDES.encrypt(value, key);
console.log(encryptedString.toString());
And i get something like this : U2FsdGVkX19eYFFHgYGCr3v9/skTOKVp0pLWRNK9JTg=
I use this encryptedString and key in other Decrypt tool online ( Which also use CryptoJS ) and got back exact value KF169841.
After sending this value and key to server ( well key isn't sending directly to server though but for test, it is ), i need to decrypt it using Java.
But i quite don't know how to decrypt it. I'm tried some code from google search but it end up wrong padding if use DESese or get wrong value if i use ECB/NoPadding.
I did try to something like setting sfg for CryptoJS side like:
mode: CryptoJS.mode.EBC,
padding: CryptoJS.pad.NoPadding
But they got javascript exception ( a is not define )
So any have any experience with CryptoJS can help me decrypt this one using java ?
=============================================================
UPDATE : Sorry here my server side code i'm using
/**
* Method To Decrypt An Ecrypted String
*/
public String decrypt(String encryptedString, String myEncryptionKey) {
String decryptedText = null;
try {
byte[] keyAsBytes = myEncryptionKey.getBytes("UTF8");
KeySpec myKeySpec = new DESedeKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory =
SecretKeyFactory.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
cipher.init(Cipher.DECRYPT_MODE, key);
// BASE64Decoder base64decoder = new BASE64Decoder();
// byte[] encryptedText = base64decoder.decodeBuffer(encryptedString);
byte[] encryptedText = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedString);
byte[] plainText = cipher.doFinal(encryptedText);
decryptedText= bytes2String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
According to the documentation, your encryptedString variable contains structured data that must be split apart to be sent to Java code. You will need to send encryptedString.iv and encryptedString.ciphertext to your Java code. If you continue to use passwords (see below), you will need to send encryptedString.salt as well.
If you pass your key as a string it will be interpreted as a password and a key will be derived from it. If you actually want to pass an explicit key, follow the documentation and specify the IV and key as suggested by the code snippet below. If you stick with supplying a password, then you must figure out the derivation scheme and use the same process in your Java code.
// Code snippet from http://code.google.com/p/crypto-js/#Custom_Key_and_IV
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
</script>
Regarding your Java code, it looks mostly OK (although there is plenty of room for error with string conversions). However, you probably want to convert your key from hex to binary rather than grabbing the bytes:
byte[] keyAsBytes = DatatypeConverter.parseHexBinary(myEncryptionKey);
This assumes you alter your JavaScript code to pass the literal key value.
You will also need to switch to DESede/CBC/PKCS5Padding and pass an IVParameterSpec object to your Cipher.init call, specifying the IV value sent from your Java Script code.
This is a follow up to this question, but I'm trying to port C# code to Java instead of Ruby code to C#, as was the case in the related question. I am trying to verify the encrypted signature returned from the Recurly.js api is valid. Unfortunately, Recurly does not have a Java library to assist with the validation, so I must implement the signature validation myself.
Per the related question above (this), the following C# code can produce the hash needed to validate the signature returned from Recurly:
var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
Recurly provides the following example data on their signature documentation page:
unencrypted verification message:
[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
private key:
0123456789ABCDEF0123456789ABCDEF
resulting signature:
0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386
Here is my Java implementation:
String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));
private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bytes = md.digest(source.getBytes("UTF-8"));
return bytes;
}
private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] bytes = baseString.getBytes("ASCII");
return Hex.encodeHexString(mac.doFinal(bytes));
}
However, when I print out the encryptedMessage variable, it does not match the message portion of the example signature. Specifically, I get a value of "c8a9188dcf85d1378976729e50f1de5093fabb78" instead of "0f5630424b32402ec03800e977cd7a8b13dbd153".
Update
Per #M.Babcock, I reran the C# code with the example data, and it returned the same output as the Java code. So it appears my hashing approach is correct, but I am passing in the wrong data (unencryptedMessage). Sigh. I will update this post if/when I can determine what the correct data to encrypt is- as the "unencrypted verification message" provided in the Recurly documentation appears to be missing something.
Update 2
The error turned out to be the "unencrypted verification message" data/format. The message in the example data does not actually encrypt to the example signature provided- so perhaps outdated documentation? At any rate, I have confirmed the Java implementation will work for real-world data. Thanks to all.
I think the problem is in your .NET code. Does Configuration.RecurlySection.Current.PrivateKey return a string? Is that value the key you expect?
Using the following code, .NET and Java return identical results.
.NET Code
string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));
Console.WriteLine(" Message: {0}", message);
Console.WriteLine(" Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine(" Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Result: c8a9188dcf85d1378976729e50f1de5093fabb78
Java
String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));
SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));
System.out.println(" Message: " + message);
System.out.println(" Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println(" Results: " + toHex(result));
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Results: c8a9188dcf85d1378976729e50f1de5093fabb78
I suspect the default encoding of the values you're working on may be different. As they do not have it specified, they will use the default encoding value of the string based on the platform you're working on.
I did a quick search to verify if this was true and it was still inconclusive, but it made me think that strings in .NET default to UTF-16 encoding, while Java defaults to UTF-8. (Can someone confirm this?)
If such's the case, then your GetBytes method with UTF-8 encoding is already producing a different output for each case.
Based on this sample code, it looks like Java expects you to have not already SHA1'd your key before creating a SecretKeySpec. Have you tried that?