AES-CBC: using CryptoJS in React-Native and CryptoSwift in Swift - react-native

I'm building a React-Native app that uses NotificationServiceExtension on iOS to intercept encrypted notifications and decrypt them before showing to the user.
On the react-native side, I encrypt the message using CryptoJS:
const originalText = 'This will be encrypted';
const theAESKeyWordArray = CryptoJS.enc.Hex.parse("000102030405060708090a0b0c0d0e0f");
const iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f"); // Test iv text is in hexadecimal and converting it to wordArray
const ciphertext = CryptoJS.AES.encrypt(originalText, theAESKeyWordArray, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: iv
}).toString();
On the NotificationServiceExtension side (Swift) using CryptoSwift, after receiving the notification, I'm trying to decode it using the following code:
The "encryptedBody" variable when printed out to console looks exactly the same as console.log(ciphertext) in react-native side. I can also see that when printed out the clearAESKey.string(encoding: .utf8) is the same as that which I used to to encrypt it on the react-native side.
let AESKey = Array<UInt8>(hex: try clearAESKey.string(encoding: .utf8))
let iv = Array<UInt8>(hex: "101112131415161718191a1b1c1d1e1f")
let bufContent: [UInt8] = [UInt8] (encryptedBody.utf8)
let aes = try AES(key: AESKey, blockMode: CBC(iv: iv), padding: .pkcs7)
let padded = Padding.pkcs7.add(to: bufContent, blockSize: AES.blockSize)
let decryptedBody = try aes.decrypt(padded)
let decryptedData = Data(decryptedBody)
let decryptedString = String(decoding: decryptedBody, as: UTF8.self)
print(decryptedString)
The printed output of decryptedString is something like this "L����JtϑJ��E7sD��)�ga�J׊p�"
Any ideas what I am doing wrong?
Thanks anyone for your help.

Key and IV must be hex decoded, the ciphertext returned by the CryptoJS code must be Base64 decoded. Before decryption, no padding must be applied:
let AESKey = [UInt8](hex: "000102030405060708090a0b0c0d0e0f")
let iv = [UInt8](hex: "101112131415161718191a1b1c1d1e1f")
let bufContent = [UInt8](base64: "GErWj4t2vW0u1o1Z9iUNo8gxkO1O0Bl8l3amQ86EKKw=")
let aes = try AES(key: AESKey, blockMode: CBC(iv: iv), padding: .pkcs7)
let decryptedBody = try aes.decrypt(bufContent)
let decryptedString = String(bytes: decryptedBody, encoding: .utf8)!
print(decryptedString) // This will be encrypted
Keep in mind that a static IV is insecure (for testing purposes this is of course OK).

Related

AES-256-CBC encryption algorithm in react-native

Hi am trying to check the compatability of encryption done on BE.
BE provided the below code `
$secret_key = VALUE1`;
$secret_iv = VALUE2;
$encrypt_method = "AES-256-CBC";
$key = hash('sha256', $secret_key);
$iv = substr(hash('sha256', $secret_iv), 0, 16);
openssl_encrypt($string, $encrypt_method, $key, 0, $iv)`
the openssl gave SNtvZ3Dpv1S88Ha6aVBdcg== when input string is abc
I tried few alogritham but I wasnt able to correctly match it with BE
he $key and $iv value I generated same as BE. but when it comes to encryption it doesnt give any result or saying Expected IV length is 16 but got 8
The following are the packages I tried
import Aes from 'react-native-aes-crypto';
Aes.encrypt(inputText, hash, _IV, 'aes-256-cbc').then(cipher => {
console.log('cipher', cipher); });
This throws an error Error: expected IV length of 16 but was 8
I checked the length of _IV and it is certanly 16
import CryptoAesCbc from 'react-native-crypto-aes-cbc';
CryptoAesCbc.encryptInBase64(
base64.encode(ivHash.substr(0, 16)),
base64.encode(hash),
'abc',
'128',
)
.then(encryptString => {
console.log('encryptString', encryptString);
})
.catch(error => {
console.log('error ', error);
});
This the encryptString value prints empty string
Please someone give me some insight into this.
FYI am checking in Android for the time being
EDIT 1
I red in some post about converting to _IV to hex
so I did this
Aes.encrypt(
inputText,
hash,
Buffer.from(_IV).toString('hex'),
'aes-256-cbc',
)
.then(cipherText => console.log('cipherText', cipherText))
.catch(error => console.log('error here', error));
It gave me wrong out out cipherText: yLvY847qCMHHGHdachjKGw==
Any help is appreciated
Thanks in advance
In the hash() function of the PHP code, the third parameter is false by default, so $key and $iv are returned as hex encoded strings and not as raw binary data.
For SHA256 with a digest output length of 32 bytes $key therefore has a length of 64 bytes, since $key is not explicitly truncated, unlike $iv.
Nevertheless, only the first 32 bytes of the 64 bytes are used for encryption, because OpenSSL/PHP silently truncates keys that are too large (and pads keys that are too short with 0x00 values) to achieve the specified length (32 bytes for AES-256-CBC).
On the CryptoJS side, both the hex encoding of key and IV must be taken into account as well as the truncation of the key.
A possible implementation with CryptoJS (referring to your solution in the chat) is:
var plaintext = "The quick brown fox";
var keyMaterial = "my key passphrase";
var ivMaterial = "my IV passphrase"
var truncHexKey = CryptoJS.SHA256(keyMaterial).toString().substr(0, 32); // hex encode and truncate
var truncHexIV = CryptoJS.SHA256(ivMaterial).toString().substr(0, 16); // hex encode and truncate
var key = CryptoJS.enc.Utf8.parse(truncHexKey);
var iv = CryptoJS.enc.Utf8.parse(truncHexIV);
var ciphertext = CryptoJS.AES.encrypt(plaintext, key, {iv: iv}); // default values: CBC, PKCS#7 padding
console.log('Ciphertext: ' + ciphertext.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
which gives the same ciphertext as the PHP code.
In general, it should also be noted that the use of upper and lower case letters is not consistently implemented in hex encoding. However, like PHP's hash() function, CryptoJS uses lowercase letters, so this is not a problem here.

Can't figure out how to send a signed POST request to OKEx

I want to send a signed POST request to Okex: Authentication Docs POST Request Docs.
I always get back an "invalid sign" error.
I successfully sent a signed GET request. For the POST you also need to add the body in the signature. If I do that, none of my signatures are valid anymore. I already verified that my signature is the same as one produced by their official Python SDK (that's why I wrote the JSON by hand. Python has spaces in the JSON). I am new to Rust so I am hoping I am missing something obvious.
OKEx client implementations in other languages: https://github.com/okcoin-okex/open-api-v3-sdk
/// [dependencies]
/// hmac="0.7.1"
/// reqwest = "0.9.18"
/// chrono = "0.4.6"
/// base64="0.10.1"
/// sha2="0.8.0"
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use chrono::prelude::{Utc, SecondsFormat};
use hmac::{Hmac, Mac};
use sha2::{Sha256};
static API_KEY: &'static str = "<insert your key!>";
static API_SECRET: &'static str = "<insert your secret!>";
static PASSPHRASE: &'static str = "<insert your passphrase!>";
fn main() {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
let method = "POST";
let request_path = "/api/spot/v3/orders";
let body_str = "{\"type\": \"market\", \"side\": \"sell\", \"instrument_id\": \"ETH-USDT\", \"size\": \"0.001\"}";
let mut signature_content = String::new();
signature_content.push_str(&timestamp);
signature_content.push_str(method);
signature_content.push_str(request_path);
signature_content.push_str(&body_str);
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_varkey(API_SECRET.as_bytes()).unwrap();
mac.input(signature_content.as_bytes());
let signature = mac.result().code();
let base64_signature = base64::encode(&signature);
let mut header_map = HeaderMap::new();
header_map.insert("OK-ACCESS-KEY", HeaderValue::from_str(API_KEY).unwrap());
header_map.insert("OK-ACCESS-SIGN", HeaderValue::from_str(&base64_signature).unwrap());
header_map.insert("OK-ACCESS-TIMESTAMP", HeaderValue::from_str(&timestamp).unwrap());
header_map.insert("OK-ACCESS-PASSPHRASE", HeaderValue::from_str(PASSPHRASE).unwrap());
header_map.insert(CONTENT_TYPE, HeaderValue::from_static("application/json; charset=UTF-8"));
let client = reqwest::Client::new();
let mut complete_url = String::from("https://okex.com");
complete_url.push_str(request_path);
let res = client
.post(complete_url.as_str())
.headers(header_map)
.body(body_str)
.send().unwrap().text();
println!("{:#?}", res);
}
This returns an "Invalid Sign" error at the moment but should return a successful http code (if enough funds are on the account).
Solution was to use "https://www.okex.com" instead of "https://okex.com. The latter produces the "Invalid Sign" error. But just for POST requests. Issue was therefore not Rust related.

HTML5 player not working on chrome

I'm new to Stackoverflow and this will be my first question. My HTML5 player works fine on Internet Explorer but doesn't work on google chrome. I'm using a PlayReady stream which is encrypted with CENC. How can I let this work on chrome? I don't have access to the servers, they're run by third parties.
Thanks
Technically it is possible to support Widevine while you're stream is PlayReady. This is possible since you use CENC. Since you don't have access to the servers like you mentioned you can use a technique called PSSH Forging. It basically replaces the pieces to make chrome think it's Widevine, since it's CENC the CDM will decrypt the video and the stream will play.
For the sake of ease i'm going to assume you use DASH.
We have here a PSSH Box:
const widevinePSSH = '0000005c7073736800000000edef8ba979d64acea3c827dcd51d21ed0000003c080112101c773709e5ab359cbed9512bc27755fa1a087573702d63656e63221848486333436557724e5a792b32564572776e64562b673d3d2a003200';
You need to replace 1c773709e5ab359cbed9512bc27755fa with your KID.
And then at the part where you insert you'r segment in the SourceBuffer (before appendSegment) you can do the following:
let segment = args[0];
segment = new Uint8Array(segment);
const newPssh = widevinePSSH.replace('1c773709e5ab359cbed9512bc27755fa', psshKid);
const subArray = new Uint8Array(DRMUtils.stringToArrayBuffer('70737368'));
let index = 0;
const found = subArray.every((item) => {
const masterIndex = segment.indexOf(item, index);
if (~masterIndex) {
index = masterIndex;
return true;
}
});
if (found) {
return originalSourceBufferAppendBuffer.apply(this, [].slice.call(args));
}
segment = DRMUtils.uInt8ArrayToHex(segment);
// Inject the forged signal
// 70737368 = pssh
segment = segment.substr(0, segment.lastIndexOf('70737368') - 8) + newPssh + segment.substr(segment.lastIndexOf('70737368') - 8);
// Fix the MOOV atom length
// 6d6f6f76 = moov
const header = segment.substr(0, segment.indexOf('6d6f6f76') - 8);
const payload = segment.substr(segment.indexOf('6d6f6f76') - 8);
const newLength = Math.floor(payload.length / 2);
segment = header + DRMUtils.intToHex(newLength, 8) + payload.substr(8);
segment = decode(segment).b;
Sadly i can only share bits and pieces but this is roughly what you should do to get it working.

Creating a private/public key with 64 characters that are already known using bitcoinjs

So I'm trying to create a private/public key from 64 characters that I already know using bitcoinjs with the code below:
key = Bitcoin.ECKey.makeRandom();
// Print your private key (in WIF format)
document.write(key.toWIF());
// => Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct
// Print your public key (toString defaults to a Bitcoin address)
document.write(key.pub.getAddress().toString());
// => 14bZ7YWde4KdRb5YN7GYkToz3EHVCvRxkF
If I try to set "key" to my 64 characters instead of "Bitcoin.ECKey.makeRandom();" it fails. Is there a method or library that I overlooked that would allow me to use the known 64 characters in order to generate the private key in wif format and the public address?
Thanks in advance to anyone that may be able to offer some help.
You should use fromWIF method to pass your own data.
from source code of eckey.js
// Static constructors
ECKey.fromWIF = function(string) {
var payload = base58check.decode(string)
var compressed = false
// Ignore the version byte
payload = payload.slice(1)
if (payload.length === 33) {
assert.strictEqual(payload[32], 0x01, 'Invalid compression flag')
// Truncate the compression flag
payload = payload.slice(0, -1)
compressed = true
}
To create WIF from your key please follow https://en.bitcoin.it/wiki/Wallet_import_format
Here is interactive tool http://gobittest.appspot.com/PrivateKey
The solution to generate private and public key:
//public-key
var address = eckey.getBitcoinAddress().toString();
var privateKeyBytesCompressed = privateKeyBytes.slice(0);
privateKeyBytesCompressed.push(0x01);
var privateKeyWIFCompressed = new Bitcoin.Address(privateKeyBytesCompressed);
privateKeyWIFCompressed.version = 0x80;
//private-key
privateKeyWIFCompressed = privateKeyWIFCompressed.toString();
Take a look at moneyart.info for beautifully designed paperwallets.

How to get correct SHA1 hash of BLOB using CryptoJS?

CryptoJS v3.1.2, sha1.js rollup
In JS I want to calculate the SHA1 of a blob before sending it to the server. On the server I want to calculate the SHA1 of the resulting file and compare it to the SHA1 received from JS. The problem is that the hash generated by CryptoJS.SHA1() is incorrect (always 9844f81e1408f6ecb932137d33bed7cfdcf518a3)
JS Code:
function uploadFileslice (slice) { // slice is a blob
var fileReader = new FileReader()
fileReader.onload = function(event){
var arrayBuffer = event.target.result
var wordArray = CryptoJS.lib.WordArray.create(arrayBuffer)
var sha1crc = CryptoJS.SHA1(wordArray).toString(CryptoJS.enc.Hex)
//etc
requestParams.append('fileslice', slice)
requestParams.append('sha1crc', sha1crc)
//etc
}
fileReader.readAsArrayBuffer(slice)
}
PHP code:
$file_crc = sha1_file($_FILES['fileslice']['tmp_name']);
if ($_REQUEST['sha1crc'] !== $file_crc) {
echo "Invalid CRC: {$_REQUEST['sha1crc']} (expected $file_crc)";
return;
}
Output:
Invalid CRC: 9844f81e1408f6ecb932137d33bed7cfdcf518a3 (expected 3ebe2cd2d8fd8d8f977b6d715f0b1adf5b08b407
I was hoping for something like myHash = CryptoJS.SHA1(blob)
From the info that you've provided I'm not sure exactly how you have things setup but in order for ArrayBuffers to be supported you have to include components/lib-typedarrays-min.js.
There's a discussion about this at https://code.google.com/p/crypto-js/issues/detail?id=67.
Hope this helps!
If you are using modules and import you can:
import Hex from 'crypto-js/enc-hex'
import WordArray from 'crypto-js/lib-typedarrays'
import sha1 from 'crypto-js/sha1'
const hash = sha1(WordArray.create(arrayBuffer)).toString(Hex)