I want to encrypt a message using RSA with a provided PEM public key in Javascript, using SubtleCrypto window.crypto.subtle and then decode it with Python (PyCryptodome) in the back-end. However, I get a ValueError: Incorrect decryption.. I'm not sure if the data is being correctly handled though. Here is my code:
JavaScript:
var publicKey;
var pemPublicKey = `public.pem key with stripped header and footer and newlines (just the base64 data)`;
function base64ToArrayBuffer(b64) {
var byteString = window.atob(b64);
var byteArray = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) { byteArray[i] = byteString.charCodeAt(i); }
return byteArray;
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); }
return window.btoa(binary);
}
window.crypto.subtle.importKey(
"spki",
base64ToArrayBuffer(pemPublicKey),
{ name: "RSA-OAEP", hash: { name: "SHA-256" } },
false,
["encrypt"])
.then(function (key) {
publicKey = key
})
console.log(publicKey)
var enc = new TextEncoder()
var encmessage = enc.encode("test14")
var encryptedData;
window.crypto.subtle.encrypt({
name: "RSA-OAEP"
}, publicKey, encmessage).then(function (encrypted) { encryptedData = encrypted })
var encodedData = arrayBufferToBase64(encryptedData);
console.log(encodedData)
What the code above does is convert the public PEM key, generate a CryptoKey object out of it (using crypto.subtle.importKey) and then encrypts a simple message "test14".
Python backend:
import base64
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import AES, PKCS1_OAEP
with open('private.pem', 'r') as f: keypair = RSA.import_key(f.read())
decryptor = PKCS1_OAEP.new(keypair)
decrypted = decryptor.decrypt(base64.b64decode(encrypted)) # encrypted is the data that is returned by JavaScript code
print(decrypted)
Directly from the documentation of Crypto.Cipher.PKCS1_OAEP.new(key, hashAlgo=None, mgfunc=None, label='', randfunc=None):
...
hashAlgo (hash object) - The hash function to use. This can be a module under Crypto.Hash or an existing hash object created from any of such modules. If not specified, Crypto.Hash.SHA1 is used.
...
Related
This is my c# code for string encryption using RijndaelManaged,
I'm not able to encrypt like same in flutter, i tried many packages. but no result.
i need to encrypt a string in flutter and i need to decrypt in c#
public static string key = Environment.GetEnvironmentVariable("ENCR_KEY");
private const int Keysize = 256;
private const int DerivationIterations = 100;
public string Encrypt(string plainText)
{
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(key, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 128;
symmetricKey.Mode = CipherMode.CBC;
//symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[16];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
public string Decrypt(string cipherText)
{
string password = key;
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
var salt = cipherBytes.Take(16).ToArray();
var iv = cipherBytes.Skip(16).Take(16).ToArray();
var encrypted = cipherBytes.Skip(32).ToArray();
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt, 100);
encryptor.Key = pdb.GetBytes(32);
encryptor.Padding = PaddingMode.PKCS7;
encryptor.Mode = CipherMode.CBC;
encryptor.IV = iv;
using (MemoryStream ms = new MemoryStream(encrypted))
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Read))
{
using (var reader = new StreamReader(cs, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
}
}
When i try encrypted cipher to decrypt in c#. I'm getting this error,
"Padding is invalid and cannot be removed."
This is my Dart code
import 'dart:convert';
import 'package:pointycastle/block/aes_fast.dart';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
const KEY_SIZE = 32; // 32 byte key for AES-256
const ITERATION_COUNT = 2;
const SALT = "XXXXXXXXXXXXXXXXX";
const INITIAL_VECTOR = "ZZZZZZZZZZZZZZZZ";
const PASS_PHRASE = "YYYYYYYYYYYYYYYYYYY";
Future<String> cryptString(String text) async {
String encryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
encryptedString = AesHelperMethod2.encrypt(
mStrPassPhrase,
toUtf8(text),
mode: AesHelperMethod2.CBC_MODE,
);
return encryptedString;
}
Future<String> decryptString(String text) async {
String decryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
decryptedString = AesHelperMethod2.decrypt(mStrPassPhrase, toUtf8(text),
mode: AesHelperMethod2.CBC_MODE);
return decryptedString;
}
///MARK: AesHelper class
class AesHelperMethod2 {
static const CBC_MODE = 'CBC';
static const CFB_MODE = 'CFB';
static Uint8List deriveKey(dynamic password,
{String salt = '',
int iterationCount = ITERATION_COUNT,
int derivedKeyLength = KEY_SIZE}) {
if (password == null || password.isEmpty) {
throw new ArgumentError('password must not be empty');
}
if (password is String) {
password = createUint8ListFromString(password);
}
Uint8List saltBytes = createUint8ListFromString(salt);
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
keyDerivator.init(params);
return keyDerivator.process(password);
}
static Uint8List pad(Uint8List src, int blockSize) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = blockSize - (src.length % blockSize);
var out = new Uint8List(src.length + padLength)..setAll(0, src);
pad.addPadding(out, src.length);
return out;
}
static Uint8List unpad(Uint8List src) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = pad.padCount(src);
int len = src.length - padLength;
return new Uint8List(len)..setRange(0, len, src);
}
static String encrypt(String password, String plaintext,
{String mode = CBC_MODE}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv = createUint8ListFromString(ivStr);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
final enc = base64.encode(cipherBytes);
print("enc : " "$enc");
return base64.encode(cipherBytes);
}
static String decrypt(String password, String ciphertext,
{String mode = CBC_MODE}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv = createUint8ListFromString(ivStr);
Uint8List cipherBytesFromEncode = base64.decode(ciphertext);
Uint8List cipherIvBytes =
new Uint8List(cipherBytesFromEncode.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytesFromEncode);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
cipher.init(false, params);
int cipherLen = cipherIvBytes.length - aes.blockSize;
Uint8List cipherBytes = new Uint8List(cipherLen)
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
Uint8List paddedText = _processBlocks(cipher, cipherBytes);
Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
var out = new Uint8List(inp.lengthInBytes);
for (var offset = 0; offset < inp.lengthInBytes;) {
var len = cipher.processBlock(inp, offset, out, offset);
offset += len;
}
return out;
}
}
///MARK: HELPERS
Uint8List createUint8ListFromString(String s) {
Uint8List ret = Uint8List.fromList(s.codeUnits);
return ret;
}
String toUtf8(value) {
var encoded = utf8.encode(value);
var decoded = utf8.decode(encoded);
return decoded;
}
String toASCII(value) {
var encoded = ascii.encode(value);
var decoded = ascii.decode(encoded);
return decoded;
}
The C# encrypt() method does the following:
Generating a random 16 bytes salt and a random 16 bytes IV
Deriving a key with PBKDF2 using the following parameters
Key size 32 bytes
Digest: Sha-1
Iteration count: 100 (generally much too small for PBKDF2!)
Encrypting with AES in CBC mode and PKCS#7 padding
Concatenating salt, IV and ciphertext in that order and Base64 encoding
These functional building blocks must be replicated in the Dart code: For this you need a function that generates random values. So far, there is no such thing in the posted code. Also, the deriveKey() function for key derivation needs to be refactored. Other functionalities that could be encapsulated in functions are encryption with AES in CBC mode and PKCS#7 Padding as well as concatenation and encoding of the data.
Possible implementation to generate random values:
SecureRandom getSecureRandom() {
List<int> seed = List<int>.generate(32, (_) => Random.secure().nextInt(256));
return FortunaRandom()..seed(KeyParameter(Uint8List.fromList(seed)));
}
Refactoring of the deriveKey() method
Uint8List deriveKey(Uint8List salt, Uint8List passphrase){
KeyDerivator derivator = KeyDerivator('SHA-1/HMAC/PBKDF2');
Pbkdf2Parameters params = Pbkdf2Parameters(salt, 100, 256~/8);
derivator.init(params);
return derivator.process(passphrase);
}
Implementation of a method that encrypts with AES in CBC mode and PKCS#7 padding:
Uint8List encryptAesCbcPkcs7(Uint8List plaintext, Uint8List key, Uint8List iv){
CBCBlockCipher cipher = CBCBlockCipher(AESEngine());
ParametersWithIV<KeyParameter> params = ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
PaddedBlockCipherImpl paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), cipher);
paddingCipher.init(true, paddingParams);
Uint8List ciphertext = paddingCipher.process(plaintext);
return ciphertext;
}
And finally, a method for concatenating and encoding the data:
String concatAndEncode(Uint8List salt, Uint8List iv, Uint8List ciphertext){
BytesBuilder saltIvCiphertext = BytesBuilder();
saltIvCiphertext.add(salt);
saltIvCiphertext.add(iv);
saltIvCiphertext.add(ciphertext);
String saltIvCiphertextB64 = base64Encode(saltIvCiphertext.toBytes());
return saltIvCiphertextB64;
}
Then these functional blocks only need to be wired:
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
...
Uint8List plaintext = Uint8List.fromList(utf8.encode("The quick brown fox jumps over the lazy dog"));
Uint8List passphrase = Uint8List.fromList(utf8.encode("my passphrase"));
// Generate random 16 bytes salt and random 16 bytes IV
SecureRandom secureRandom = getSecureRandom();
Uint8List salt = secureRandom.nextBytes(16);
Uint8List iv = secureRandom.nextBytes(16);
// Derive 32 bytes key via PBKDF2
Uint8List key = deriveKey(salt, passphrase);
// Encrypt with AES-256/CBC/PKCS#7 padding
Uint8List ciphertext = encryptAesCbcPkcs7(plaintext, key, iv);
// Concat salt|nonce|ciphertext and Base64 encode
String saltIvCiphertextB64 = concatAndEncode(salt, iv, ciphertext);
print(saltIvCiphertextB64); // e.g. 3igL9PVjgWpCTwYHP2GluZ/8lUaNblnGFEjZFDEiGvdnjoR/RkXIEtcPmgsnC4MmsfesGXo8Jls2vnCISoVAkzIZvadxbw5Dq1QddeMPnS0=
A ciphertext generated with this Dart code can be decrypted with the C# code.
We have this code on encrypting/decrypting a particular message and I'd like to decrypt the value in Flutter (dart).
/* Encrypt text
* #param text
*/
export const encrypt = (text: string): string => {
const encJson = CryptoJS.AES.encrypt(JSON.stringify(text), SECRET_KEY).toString();
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encJson));
};
/* Decrypt text
* #param ciphertext
*/
export const decrypt = (ciphertext: string): string => {
const decData = CryptoJS.enc.Base64.parse(ciphertext).toString(CryptoJS.enc.Utf8);
const bytes = CryptoJS.AES.decrypt(decData, SECRET_KEY).toString(CryptoJS.enc.Utf8);
return JSON.parse(bytes);
};
I have tried the example mentioned in this article but could not make it work.
https://medium.com/#chingsuehok/cryptojs-aes-encryption-decryption-for-flutter-dart-7ca123bd7464
I really appreciate if anyone can help or point me out on what to change on my code.
Current code:
String decryptAESCryptoJS(String encrypted, String passphrase) {
try {
Uint8List encryptedBytesWithSalt = base64.decode(encrypted);
Uint8List encryptedBytes =
encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
final salt = encryptedBytesWithSalt.sublist(8, 16);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final decrypted =
encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
return decrypted;
} catch (error) {
throw error;
}
}
Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
var password = createUint8ListFromString(passphrase);
Uint8List concatenatedHashes = Uint8List(0);
List<int> currentHash = Uint8List(0);
bool enoughBytesForKey = false;
Uint8List preHash = Uint8List(0);
while (!enoughBytesForKey) {
int preHashLength = currentHash.length + password.length + salt.length;
if (currentHash.length > 0)
preHash = Uint8List.fromList(currentHash + password + salt);
else
preHash = Uint8List.fromList(password + salt);
currentHash = md5.convert(preHash).bytes;
concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
}
var keyBtyes = concatenatedHashes.sublist(0, 32);
var ivBtyes = concatenatedHashes.sublist(32, 48);
return new Tuple2(keyBtyes, ivBtyes);
}
Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
Uint8List genRandomWithNonZero(int seedLength) {
final random = Random.secure();
const int randomMax = 245;
final Uint8List uint8list = Uint8List(seedLength);
for (int i = 0; i < seedLength; i++) {
uint8list[i] = random.nextInt(randomMax) + 1;
}
return uint8list;
}
The CryptoJS code unnecessarily Base64 encodes the ciphertext twice during encryption. So the most reasonable solution would be to fix the CryptoJS code.
In encrypt(), encJson is already the Base64 encoded ciphertext, i.e. the body of the encrypt() method should actually be:
return CryptoJS.AES.encrypt(JSON.stringify(text), SECRET_KEY).toString();
and analogously the body of decrypt():
const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY).toString(CryptoJS.enc.Utf8);
return JSON.parse(bytes);
With this fix, successful decryption with the unmodified Dart code is possible.
If for some reason the CryptoJS code must not be changed, the Dart code in decryptAESCryptoJS() must also Base64 decode twice:
Uint8List encryptedBytesWithSalt = base64.decode(utf8.decode(base64.decode(encrypted)));
With this fix, the ciphertext of the unmodified CryptoJS code can be successfully decrypted.
I want to convert below PHP script to dart i tried a lot case but nothing help me.
I have tried following code; But throw an exception here encrypter.decrypt method.
import 'package:encrypt/encrypt.dart' as EncryptPack;
import 'package:crypto/crypto.dart' as CryptoPack;
import 'dart:convert' as ConvertPack;
void main(List<String> arguments) {
var decrypt = extractPayload('$encryptedResopnse');
print(decrypt);
}
String extractPayload(String encryptedResopnse) {
if (encryptedResopnse == null) {
return '';
}
var separated = encryptedResopnse.split(':');
var secret = 'abcd123';
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = CryptoPack.sha256.convert(data).toString().substring(0, 16);
var salt = CryptoPack.sha256.convert(data).toString().substring(16, 32);
var cipherText = CryptoPack.sha256.convert(data).toString().substring(64);
print('cipherText : ${cipherText}');
var ivObj = EncryptPack.IV.fromBase64(iv);
var generator = PBKDF2(hashAlgorithm: CryptoPack.sha1);
var hash = generator.generateBase64Key(secret, salt, 2048, 32);
print('hash : $hash');
var keyObj = EncryptPack.Key.fromBase64(hash);
final encrypter = EncryptPack.Encrypter(
EncryptPack.AES(keyObj, mode: EncryptPack.AESMode.cbc)); // Apply CBC mode
print(cipherText);
var firstBase64Decoding = cipherText; // First Base64 decoding
print(firstBase64Decoding);
final decrypted = encrypter.decrypt(
EncryptPack.Encrypted.fromBase64(firstBase64Decoding),
iv: ivObj);
return decrypted;
}
for demo content ;
Initialize the aes_secret
$aes_secret = '123456ac';
Demo content;
$encryptedResopnse = "dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91";
class AesEncryption {
private static $encryptionMethod = 'aes-256-cbc';
private static $blockSize = 16;
private static $keySize = 32; // in bytes - so 256 bit for aes-256
private static $iterations = 2048;
public static function sign($data, $key) {
return hash_hmac('sha256', $data, $key);
}
/**
* #param string $encryptedContent
* #param string $secret
* #return string
*/
public static function decrypt(string $encryptedContent, string $secret) {
if (!$encryptedContent) {
return "";
}
// Separate payload from potential hmac
$separated = explode(":", trim($encryptedContent));
// Extract HMAC if signed
$hmac = (isset($separated[1])) ? $separated[1] : null;
// Convert data-string to array
$data = base64_decode($separated[0]);
// Then we remove the iv and salt to fetch the original text
$iv = substr($data, 0, self::$blockSize);
//echo($iv);
$salt = substr($data, self::$blockSize, self::$blockSize);
// We finally extract the ciphertext
$cipherText = substr($data, self::$blockSize * 2);
// Generate Key
$key = hash_pbkdf2('sha1', $secret, $salt, self::$iterations, self::$keySize, true);
// Check https://www.php.net/manual/en/function.openssl-decrypt.php
return openssl_decrypt($cipherText, self::$encryptionMethod, $key, OPENSSL_RAW_DATA, $iv);
}
}
The generateBase64Key or generateKey methods of the PBKDF2 package expect a string for the salt. In the implementation of these methods, the salt is UTF8 encoded.
Typically, a salt is randomly generated and therefore contains byte sequences that are incompatible with the UTF8 encoding. It is not known how the salt used here was generated. However, it contains (like a randomly generated salt) byte sequences that are incompatible with the UTF8 encoding. For this reason the PBKDF2 package applied is unsuitable for key derivation in this case. In my opinion, the choice of the string type for a salt is a poor design.
The cryptography package, on the other hand, provides a Pbkdf2 implementation that processes the salt as a Uint8List and is thus suitable. This library also supports AES/CBC, so it makes sense to use this library for the decryption as well.
The following implementation decrypts the posted ciphertext:
var encryptedResopnse = 'dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91aMT4xm7FWB/RhmUN8hfsHk7EATW8CkRGF4zFGKKdeN9zzGM0ViZYv30PARg8W2SJRKoZkaMOgZXtE/8D9fWzrmNDdHBCbMt0yrGycBbn8/b3JLQkcqxzY6VnWrBR1VJ66OB1mH5i6ejDrkxLx5VvkdKf3fcoKEZ/FptZK4zUwXgHJIF/YLChsYUj2mX9Ox18ZZi9vBG9L5vONc0GuQ31FzjwG77yGrJrS4mVi76uaifu7Thd6TiYXuu7OaFBl9+lPMvfHf+wWRqLQbgDoVtOXvND5e4LncWPHZbEjHGwO9I/MnVjMnH6nSbKER63Na8XBUIwsSlJwrswa3fLNInJA1/qGBb9nrVNzKLRfvku1UPvavDP1WxsTEzg0gH8Ui6KzBoBOd9IK/7ZtmSmSug5Ig8GAZ0R/kR7DnSs4ekxKxmcCJ95YVyf9fx0Vlw2oB9iOoUaHM3OITeldfMtoM=';
var secret = 'abcd123';
var decrypt = extractPayload(encryptedResopnse, secret);
print(decrypt);
with
import 'dart:convert' as ConvertPack;
import 'package:cryptography/cryptography.dart' as CryptographyPack;
...
String extractPayload(String encryptedResopnse, String secret) {
if (encryptedResopnse == null) {
return '';
}
// Separate data
// Note: Authentication not considered (separated: size = 1; see ciphertext and PHP code (hmac derived but unused))
var separated = encryptedResopnse.split(':');
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = data.sublist(0, 16);
var salt = data.sublist(16, 32);
var cipherText = data.sublist(32);
// Derive key
var generator = CryptographyPack.Pbkdf2(
macAlgorithm: CryptographyPack.Hmac(CryptographyPack.sha1),
iterations: 2048,
bits: 256,
);
var hash = generator.deriveBitsSync(
ConvertPack.utf8.encode(secret),
nonce: CryptographyPack.Nonce(salt)
);
// Decrypt
var decrypted = CryptographyPack.aesCbc.decryptSync(
cipherText,
secretKey: CryptographyPack.SecretKey(hash),
nonce: CryptographyPack.Nonce(iv));
var plaintext = ConvertPack.utf8.decode(decrypted);
return plaintext;
}
and returns as result:
{"nofollow":false,"id":"2226521","title":"When You Say Nothing At All","album":"Ronan","albumID":"237798","artist":"Ronan Keating","artistID":"52715","track":"6","year":"1999","duration":"258.00","coverArt":"323816","ArtistArt":1002340723,"allowoffline":1,"genre":"Pop","AlbumArt":"323816","keywords":["When You Say Nothing At All","Ronan Keating","Ronan"],"languageid":2,"bitrates":"24,256","hexcolor":"#b43931","cleardetails":1,"bitrate":64,"size":"2108905","releasedate":"1999-01-01","explicit":"0","extras":"eyJyZXF1ZXN0dHlwZWlkIjo...","saveprogress":0,"lyrics":"true","is_podcast":0,"is_original":1,"location":"https:\\\/\\\/some audio file\u2019s URL","debugurl":"http:\\\/\\\/some URL","debugurldata":"http:\\\/\\\/some URL","hash":"b1229af8b0078e0b9ec9e203e3b32b7c","plays":"593963","likes":"13705"}
The cryptography package must be referenced in the pubspec.yaml in the dependencies section, here:
cryptography: ^1.4.1
I am using AWS KMS with ECC_SECG_P256K1 key. When I retreive the
public key via aws-sdk the key is 88 bytes, which suppoed to be 64 bytes (as shown in the code)
Even more the size of signature is varying between 70,71,72, which
means we can't calculate the (r,s) values based on r=[0:32],s=[32,64]
var kms = new AWS.KMS();
var pubKeyParam = {
KeyId: 'xxxxxxxx', /* required */
};
kms.getPublicKey(pubKeyParam, function(err, data) {
if (err) console.log(err, err.stack);
else
publicKey = data.PublicKey
console.log(publicKey.length) <-- 88 bytes not 64 bytes
});
Thanks in advance for help
KMS Public Key Parsing
KMS is returning the public key in ASN.1 format.
If you convert the key using publicKeyFromAsn1 here, it returns 64 bytes:
import * as asn1js from 'asn1js';
function toArrayBuffer(buffer: Buffer): ArrayBuffer {
const ab = new ArrayBuffer(buffer.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
// call this with your KMS public key
function publicKeyFromAsn1(buf: Buffer): Buffer {
const { result } = asn1js.fromBER(toArrayBuffer(buf));
const values = (result as asn1js.Sequence).valueBlock.value;
const value = values[1] as asn1js.BitString;
return Buffer.from(value.valueBlock.valueHex.slice(1));
}
KMS Signature Parsing
The KMS Signature is in DER format (which is valid BER). It ends up looking like this: 30440220{r}0220{s} Here is some parsing logic to help you extract r & s.
import * as asn1js from 'asn1js';
function toArrayBuffer(buffer: Buffer): ArrayBuffer {
const ab = new ArrayBuffer(buffer.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
//call this with your signature buffer
function parseBERSignature(sig: Buffer): { r: Buffer; s: Buffer } {
const { result } = asn1js.fromBER(toArrayBuffer(sig));
const part1 = (result as asn1js.Sequence).valueBlock.value[0] as asn1js.BitString;
const part2 = (result as asn1js.Sequence).valueBlock.value[1] as asn1js.BitString;
return {
r: Buffer.from(part1.valueBlock.valueHex),
s: Buffer.from(part2.valueBlock.valueHex),
};
}
All information i got are encrypted data (AES) and a key. The data must be a URL. I 've tried so many code-snippets (from stackoverflow) and found one snippets that worked for me partially.
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
var iv = btoa(rawData.substring(0,16));
var crypttext = btoa(rawData.substring(16));
// Decrypt...
var plaintextArray = CryptoJS.AES.decrypt(
{
ciphertext: CryptoJS.enc.Base64.parse(crypttext),
salt: ""
},
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Base64.parse(iv) }
);
// Convert hex string to ASCII.
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
console.log(hex2a(plaintextArray.toString()));
The URL must be
http://test-example.com/hjhdsdfuisd
but the output is only
-example.com/hjhdsdfuisd.
I changed
var crypttext = btoa(rawData.substring(16));
to
var crypttext = btoa(rawData);
and got
ô#XÍäÜ7±H4-example.com/hjhdsdfuisd.
What is my mistake?