How to decrypt message that's encrypted in AES-GCM-256? - encryption

I need to make a client-server connection from node.js/javascript server to a client written in Rust. The message had to be encrypted with AES-GCM-256. While in Rust (version 1.67.0) I use aes-gcm crate.
The Rust code below throws an error: aead::Error. What's wrong with the cipher.decrypt() here ?
I'm sure that the node.js implementation is correct. I think that the key variable in Rust code should be the same key from the node.js (12341234123412341234123412341234)
Implementation in Node.js / sender side
const crypto = require('crypto')
const aes256gcm = (key) => {
const encrypt = (str) => {
const iv = new crypto.randomBytes(12);
const ivString = iv.toString("base64")
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let enc1 = cipher.update(str, 'utf8');
let enc2 = cipher.final();
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
return { result, iv: ivString }
};
const decrypt = (enc) => {
enc = Buffer.from(enc, "base64");
const iv = enc.slice(enc.length - 28, enc.length - 16);
const tag = enc.slice(enc.length - 16);
enc = enc.slice(0, enc.length - 28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
let str = decipher.update(enc, null, 'utf8');
str += decipher.final('utf8');
return str;
};
return {
encrypt,
decrypt,
};
};
const cipher = aes256gcm("12341234123412341234123412341234"); // just a test key must be 32
const ct = cipher.encrypt('Hello world!!!');
console.log("encrypted message: ", ct.result)
console.log("iv / nonce : ", ct.iv)
const pt = cipher.decrypt(ct.result);
console.log("decrypted message: ", pt); // this works flawlessly!
Encryption Result by Node.js
encrypted message: Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd
iv / nonce : fvXWsQhmTtDJsGFv
decrypted message: Hello world!!!
The decryption part (Rust) / Receiver Side
use aes_gcm::{
aead::{Aead, KeyInit, OsRng},
Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
fn main() {
// master key from sender
let master = "12341234123412341234123412341234".as_bytes();
let cipher = Aes256Gcm::new_from_slice(master).unwrap();
// nonce / iv from sender
let nonce_str_base64 = "fvXWsQhmTtDJsGFv";
let nonce_str: Vec<u8> = general_purpose::STANDARD.decode(nonce_str_base64).unwrap();
let nonce = Nonce::from_slice(&nonce_str); // 96-bits; unique per message
// encrypted text from sender
let ciphertext_base64 = "Zf5aB0bbVGGX3k9Yt6x+9daxCGZO0MmwYW8VUsOY4j3gNYXP47hvfGgd";
let ciphertext = general_purpose::STANDARD.decode(ciphertext_base64).unwrap();
// gets aead::Error here
match cipher.decrypt(nonce, ciphertext.as_slice()) {
Ok(decrypted) => {
let result = String::from_utf8(decrypted).unwrap();
println!("result: {}", result);
}
Err(err) => print!("{}", err), <--- prints error: aead::Error
};
}

AES-256-GCM consists of three parts:
payload or ciphered text,
iv or nonce, a unique random number that generated once
and the tag that is part of the authentication that ensures the encrypted message has not been altered
The aes_gcm crate uses payload + tag for decryption. So the solution is to remove the iv part from the result message. I just change the sender side (Node.js) from this:
let result = Buffer.concat([enc1, enc2, iv, cipher.getAuthTag()]).toString("base64");
to this:
let result = Buffer.concat([enc1, enc2, cipher.getAuthTag()]).toString("base64");
the nonce/iv part is supplied to cipher.decrypt through function parameter

Related

Is phpseclib AES-GCM encryption compatible with javascript WebCrypto?

I'm trying to encrypt/decrypt symmetrically from php/phpseclib to js/WebCrypto(SubtleCrypto). The algorithm is AES-GCM with PBKDF2 derivation and also with plain key. I had no success. The error received from the window.crypto.subtle.decrypt() function is:
OperationError: The operation failed for an operation-specific reason
RSA-OAEP works without any problems.
Did anybody do this before - is it possible at all? I didn't find anything that confirms or denies a compatibility between these modules.
Edit: adding code example
encryption:
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/../vendor/autoload.php');
use phpseclib3\Crypt\AES;
$TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
$TEST_AES_IV = "CRKTyQoWdWB2n56f";
$message = "123&abc";
$aes = new AES('gcm');
$aes->setKey($TEST_AES_KEY);
$aes->setNonce($TEST_AES_IV);
$ciphertext = $aes->encrypt($message);
$tag = $aes->getTag();
$ciphertextBase64 = base64_encode($ciphertext . $tag);
echo $ciphertextBase64;
decryption:
<!DOCTYPE html>
<html>
<script>
function _base64ToArrayBuffer(base64) {
var binary_string = atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
async function _importKeyAes(key) {
return await window.crypto.subtle.importKey(
"raw",
key,
{ name: "AES-GCM" },
false,
["encrypt", "decrypt"]
);
}
async function decryptMessageSymetric(key, data, iv) {
keyArrayBuffer = _base64ToArrayBuffer(key);
key = await _importKeyAes(keyArrayBuffer);
iv = _base64ToArrayBuffer(iv);
data = _base64ToArrayBuffer(data);
result = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
key,
data
);
return new TextDecoder().decode(result);
}
TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
TEST_AES_IV = "CRKTyQoWdWB2n56f";
messageEncrypted = "LATYboD/FztIKGVkiJNWHOP72C77FiY="; // result from phpseclib encryption
result = decryptMessageSymetric(TEST_AES_KEY, messageEncrypted, TEST_AES_IV);
console.log(result);
</script>
</html>
There are only two minor encoding bugs:
In the phpseclib code the key is not Base64 encoded, in the WebCrypto code it is Base64 encoded. This needs to be changed so that both sides use the same key.
For the test below I arbitrarily decide to use the WebCrypto solution, i.e. in the phpseclib code a Base64 encoding is added:
$TEST_AES_KEY = base64_decode("TWw4QCkeZEnXoCDkI1GEHQ==");
This produces a 16 bytes key so that AES-128 is applied (note that the phpseclib solution would also be possible, since the Base64 encoded key is 24 bytes in size and corresponds to AES-192; no matter which key is applied in the end, the important thing is that on both sides the same key must be used).
Running the phpseclib code again gives the following ciphertext:
7K+HAB7Ch9V4jJ1XJPM0sANXA2ocJok=
In the WebCrypto code, this new ciphertext is now used.
In the WebCrypto code the 16 bytes IV is Base64 decoded. This creates an IV that is too short for AES. Therefore the Base64 decoding is removed and a UTF-8 encoding (analogous to the phpseclib code) is performed:
iv = new TextEncoder().encode(iv);
With these changes decryption is successful:
(async () => {
function _base64ToArrayBuffer(base64) {
var binary_string = atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
async function _importKeyAes(key) {
return await window.crypto.subtle.importKey(
"raw",
key,
{ name: "AES-GCM" },
false,
["encrypt", "decrypt"]
);
}
async function decryptMessageSymetric(key, data, iv) {
keyArrayBuffer = _base64ToArrayBuffer(key);
key = await _importKeyAes(keyArrayBuffer);
iv = new TextEncoder().encode(iv); // Remove Base64 decoding
data = _base64ToArrayBuffer(data);
result = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
key,
data
);
return new TextDecoder().decode(result);
}
TEST_AES_KEY = "TWw4QCkeZEnXoCDkI1GEHQ==";
TEST_AES_IV = "CRKTyQoWdWB2n56f";
messageEncrypted = "7K+HAB7Ch9V4jJ1XJPM0sANXA2ocJok="; // Apply modified ciphertext
result = await decryptMessageSymetric(TEST_AES_KEY, messageEncrypted, TEST_AES_IV);
console.log(result);
})();
Note that a static IV is a serious security risk for GCM, s. here.

WebCrypto decrypt with AES-GCM returns 'DOMException' when data comes from socket.io server

I am writing an encrypted chat using socket.io (node.js)
Client1 encryption:
let encryptedData = await encrypt(text);
//encryptedData returns: {message: ArrayBuffer type, iv: Uint8Array type}
socket.emit('message', encryptedData); //Send to server
Server:
socket.on('message' (data) => {
io.to(client2).emit('answer', data); // Sends the received encrypted message to Client2
});
Client2 decryption:
socket.on('answer', async function(answer){
//answer returns: {message: ArrayBuffer type, iv: ArrayBuffer type} //Why `iv` is ArrayBuffer instead of Uint8Array?
let decryptedData = await decrypt(answer.message, answer.iv); //Returns DOMException !
//I've also tried converting answer.iv to Uint8Array type, but nothing has changed.
});
Here is encrypt/decrypt code (client side):
async function encrypt(plainText){
currentIV = window.crypto.getRandomValues(new Uint8Array(12));
let message = new TextEncoder().encode(plainText);
return {
message: await window.crypto.subtle.encrypt({
name: "AES-GCM",
iv: currentIV },
secret,
message),
iv: currentIV
}
}
async function decrypt(encryptMessage, iv){
let decryptMessage = await window.crypto.subtle.decrypt({
name: "AES-GCM",
iv: iv },
secret,
encryptMessage);
return new TextDecoder().decode(decryptMessage);
}
Two clients share the same secret, one of them encrypts the message before sending, then sends the encrypted message to the other client as an ArrayBuffer, and iv as a Uint8Array.
On the other side, the user receives this data in a slightly different form, I don't understand why. It receives message as an ArrayBuffer, but iv instead of type Uint8Array gets ArrayBuffer type.
The encryption/decryption functions work well as the message is successfully decrypted before it is sent:
Client1:
let encryptedData = await encrypt(text);
let decryptedData = await decrypt(encryptedData.message, encryptedData.iv);
console.log(decryptedData); // Shows the decrypted message
socket.emit('message', encryptedData);
But if you send them through the socket server, an error will appear.
I will be very happy if someone knows how to help, thanks in advance.
Edit:
Before sending the iv, I converted it to an ArrayBuffer, and then on the client2 after receiving I converted it back to Unit8Array:
Client1:
let encryptedData = await encrypt(text);
console.log({message: encryptedData.message, iv: encryptedData.iv.buffer});
socket.emit('message', {message: encryptedData.message, iv: encryptedData.iv.buffer});
Client2:
socket.on('answer', async function(answer){
console.log(answer);
let decryptedData = await decrypt(answer.message, new Uint8Array(answer.iv));
});
But this did not help, the same error still appears.
I printed the data to the console before sending and after, to compare, and noticed that the [[ArrayBufferData]] property does not match:
Before sending:
> iv: ArrayBuffer(12)
byteLength: (...)
[[Prototype]]: ArrayBuffer
[[Int8Array]]: Int8Array(12)
[[Uint8Array]]: Uint8Array(12)
[[Int16Array]]: Int16Array(6)
[[Int32Array]]: Int32Array(3)
[[ArrayBufferByteLength]]: 12
[[ArrayBufferData]]: "0x612a02018000"
> message: ArrayBuffer(17)
byteLength: (...)
[[Prototype]]: ArrayBuffer
[[Int8Array]]: Int8Array(17)
[[Uint8Array]]: Uint8Array(17)
[[ArrayBufferByteLength]]: 17
[[ArrayBufferData]]: "0x612a0201c000"
[[Prototype]]: Object
After receiving:
> iv: ArrayBuffer(12)
byteLength: (...)
[[Prototype]]: ArrayBuffer
[[Int8Array]]: Int8Array(12)
[[Uint8Array]]: Uint8Array(12)
[[Int16Array]]: Int16Array(6)
[[Int32Array]]: Int32Array(3)
[[ArrayBufferByteLength]]: 12
[[ArrayBufferData]]: "0x302c01c1c000" // This property is not the same as before the submission.
> message: ArrayBuffer(17)
byteLength: (...)
[[Prototype]]: ArrayBuffer
[[Int8Array]]: Int8Array(17)
[[Uint8Array]]: Uint8Array(17)
[[ArrayBufferByteLength]]: 17
[[ArrayBufferData]]: "0x302c01c18000" // This property is not the same as before the submission
[[Prototype]]: Object
Someone understands why this behavior?

Type of expression is ambiguous without more context CryptoSwift Swift 4

When I am using Crypto Swift in Swift 4 , it is giving below error
Type of expression is ambiguous without more context
Below is the code which I have posted for encryption
func aesEncrypt(_ key: String, iv: String) throws -> String
{
let data = self.data(using: String.Encoding.utf8)
// Next Line is giving error
let enc = try! AES(key: key, iv: iv, blockMode: .CBC, padding: .pkcs7).encrypt([UInt8](data!))
let encData = Data(enc!)
let base64String = encData.base64EncodedString()
let result = String(base64String)
return result
}
extension String {
func aesEncrypt(key: String) throws -> String {
// Encryption
let data = self.data(using: .utf8)!
let password = key
let ciphertext = RNCryptor.encrypt(data: data, withPassword: password)
let encryptedData = Data(ciphertext)
let stringEncrypt: String = encryptedData.base64EncodedString()
print("encryptedData: \(stringEncrypt)")
return stringEncrypt
}
func aesDecrypt(key: String) throws -> String {
// Decryption
let data = Data(base64Encoded: self)!
let password = key
do {
let ciphertext = try RNCryptor.decrypt(data: data, withPassword: password)
let decryptedData = Data(ciphertext)
let stringDecrypt = String(bytes: decryptedData, encoding: .utf8) ?? "Could not decrypt"
print("decryptedData: \(stringDecrypt)")
return stringDecrypt
} catch {
print(error)
return "Error"
}
}
func aesEncrypt(key: String, iv: String) throws -> String {
let data: Array<UInt8> = (self.data(using: .utf8)?.bytes)!
let key: Array<UInt8> = (key.data(using: .utf8)?.bytes)!
let iv: Array<UInt8> = (iv.data(using: .utf8)?.bytes)!
do {
let encrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).encrypt(data)
let encryptedData = Data(encrypted)
let decrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(encrypted)
let decryptedData = Data(decrypted)
let str = String.init(data: decryptedData, encoding: .utf8)
print(str ?? String())
return encryptedData.base64EncodedString()
} catch {
print(error)
return "error"
}
}
func aesDecrypt(key: String, iv: String) throws -> String {
let data: Array<UInt8> = (Data(base64Encoded: self)?.bytes)!
let key: Array<UInt8> = (key.data(using: .utf8)?.bytes)!
let iv: Array<UInt8> = (iv.data(using: .utf8)?.bytes)!
do {
let decrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(data)
let decryptedData = Data(decrypted)
guard let value = String.init(data: decryptedData, encoding: .utf8) else {
return "error"
}
return value
} catch {
print(error)
return "error"
}
}
}
Don't use String directly, use Data or [UInt8]. The CryptoSwift has the convenient conversions helpers, to make it easier: https://github.com/krzyzanowskim/CryptoSwift#basics

ASP.Net MVC 5 How to encrypt JWT Tokens

I have gone throw few posts about using JWT in ASP.Net MVC, which guides how to issue and consume Signed JSON Web Tokens.
Can anyone please guide how to issue and consume encrypted JWT following the JSON Web Encryption (JWE) specifications in case we need to transmit some sensitive data in the JWT payload.
Understanding JWT
JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JavaScript Object Notation (JSON) object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or MACed and/or encrypted.
What JWT?
https://jwt.io/introduction/
Json Web Token Standards
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-25
Anatomy of JWT
https://scotch.io/tutorials/the-anatomy-of-a-json-web-token
Creating JSON Web Token in JavaScript
https://www.jonathan-petitcolas.com/2014/11/27/creating-json-web-token-in-javascript.html
Now, We understand JWT call and how we can serve it from server side.
Here i have HTML page in which I have button and also set some custom parameters.
<script src="//cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/hmac-sha256.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/enc-base64-min.js"></script>
<script language="JavaScript" type="text/javascript" src="https://kjur.github.io/jsrsasign/jsrsasign-latest-all-min.js"></script>
<script type="text/javascript">
$(function () {
$("#btnJWTApi").click(function () {
// Defining our token parts
// You can use one of these, as alg
// HS256, HS386, HS512
// Always keep type as JWT
var header = {
"alg": "HS256",
"typ": "JWT"
};
var tNow = KJUR.jws.IntDate.getNow();
var tEnd = KJUR.jws.IntDate.getNow() + 60 * 5;
// dynamically pass these data using a function
var data = {
"appId": "yourAppId",
"iat": tNow,
// iat (issued at time) should be set to time when request has been generated
"exp": tEnd,
// exp (expiration) should not be more than 5 minutes from now, this is to prevent Replay Attacks
"method": "TestMethod",
"Q": "test",
"SecretKey": "MySecretKey"
};
// Secret key is used for calculating and verifying the signature.
// The secret signing key MUST only be accessible by the issuer and the User,
// it should not be accessible outside of these two parties.
// Use the Secret you set during user registration from the Plugin
var secret = btoa('MySecret ');
function base64url(source) {
// Encode in classical base64
encodedSource = CryptoJS.enc.Base64.stringify(source);
// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');
// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');
return encodedSource;
}
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);
var signature = encodedHeader + "." + encodedData;
signature = CryptoJS.HmacSHA256(signature, secret);
signature = base64url(signature);
var targetEle = $("#data");
$.ajax(
{
type: "POST",
url: "http://localhost:12345/api/v1/MyController/SecureMethod",
data: '{"token":"' + encodedHeader + "." + encodedData + "." + signature + '"}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
targetEle.html("<pre>" + JSON.stringify(data, null, '\t') + "</pre>");
},
error: function () {
alert('error');
}
});
});
});
</script>
This call will generate encrypted token which include appId,secret and our payload data with method name.
(Here create one common method, which call first and then according to passing data in a token further method will be call)
This will call your method SecureMethod instead of direct TestMethod.
And decrypt token.
public string SecureMethod(dynamic tokenObject)
{
//save at a time of user registration.
string applicationID = appSecret get from database;
string secretKey = appSecret get from database;
}
var bytes = Encoding.UTF8.GetBytes(secretKey);
var secret = Convert.ToBase64String(bytes);
var jwtDecryption = JsonWebToken.DecodeToObject(token, secret, true, true);
var jsonObj = JObject.FromObject(jwtDecryption);
string appId = jsonObj["appId"].Value<string>();
if (appId.Equals(applicationID)
{
object restService = new MyController();
var method = restService.GetType().GetMethod(jsonObj["method"].ToString(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
if (method != null)
{
var parameters = method.GetParameters().Select(p => Convert.ChangeType(jsonObj[p.Name].ToString(), p.ParameterType)).ToArray();
object response = method.Invoke(restService, parameters); //your actual method should
return new JavaScriptSerializer().Serialize(response);
}
method.Invoke(restService, parameters); will have method name and parameter so it'll called your method and pass parameters.
public IHttpActionResult TestMethod([FromBody]Response model)
{
// you will get parameters in a model
return Ok();
}
Any suggestion welcome!

convert NSData to NSString always return nil

I have a problem that I can't successfully convert NSData to NSString
I use google drive sdks to get content of my google doc using
var fetcher:GTMHTTPFetcher = service.fetcherService.fetcherWithURLString(downloadurl as! String)
fetcher.beginFetchWithCompletionHandler({(olddata:NSData!, error:NSError!) in
if(error == nil) {
print(olddata)
}
else {
print("error is \(error)")
}
})
olddata was a NSData (with no optional)
And I want to convert to NSString
but it always return nil when using basic conversion
let myStringFromData = NSString(data: olddata, encoding: NSUTF8StringEncoding)
how can i fix this
import Foundation
let string = "what i would like to send to server as utf8 text"
var utf8text = string.utf8.map { $0 }
utf8text.append(0)
let data = NSData(bytes: &utf8text, length: utf8text.count)
// ..... upload, download, data is now received NSData from server
let p = UnsafePointer<Int8>(data.bytes)
if let received = String.fromCString(p) {
print(received) // what i would like to send to server as utf8 text
}else {
print("error by reconstructing string from data")
}
or
let string = "what i would like to send to server as utf8 text"
var buffer = string.nulTerminatedUTF8.map{ $0 }
let data = NSData(bytes: &buffer, length: buffer.count)
// ..... upload, download, data is now received NSData from server
let p = UnsafePointer<Int8>(data.bytes)
if let received = String.fromCString(p) {
print(received) // what i would like to send to server as utf8 text
}else {
print("error by reconstructing string from data")
}
or
let buffer = "what i would like to send to server as utf8 text".nulTerminatedUTF8
let data = buffer.withUnsafeBufferPointer {
NSData(bytes: $0.baseAddress, length: $0.count)
}

Resources