I was wondering how do I solve this problem. I generate RSA-OAEP keypair using WebCrypto API, then I export private key in pkcs8 from the keypair which exports as ArrayBuffer and I want to encode this ArrayBuffer into base64 so I can store it as a PEM.
In this testing example I am exporting key as pkcs8 and importing this pkcs8 back to CryptoKey. The problem is that sometimes it works and sometimes it does not.
These are results of the code:
NOTE: Only happens one of these states not all at once.
NOTE2: This example does not contain -----BEGIN PRIVATE KEY----- prefix and suffix I am only encoding the key.
Case1: Uncaught (in promise) URIError: URI malformed(…)b64DecodeUnicode # try.php:20b64toab # try.php:70wayBack # try.php:66(anonymous function) # try.php:56
Case2: undefined:1 Uncaught (in promise) DOMException
Case3: OK - works all the way back.
I don't know what causes the errors but I think it has something to do with base64 encoding. As I said sometimes private key generates OK and sometimes not.
Thank you very much for every help in advance.
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
function addNewLines(str) {
var finalString = '';
for(var i=0; i < str.length; i++) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
return finalString;
}
window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
true,
["encrypt", "decrypt"]
).then(function(keyPair) {
window.crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey
).then(function(exportedPrivateKey) {
var byteArray = new Uint8Array(exportedPrivateKey);
console.log(byteArray);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
wayBack(addNewLines(b64EncodeUnicode(byteString)));
});
});
function wayBack(pem) {
var lines = pem.split('\n');
var encodedString = '';
for(var i=0; i < lines.length; i++) {
encodedString += lines[i].trim();
}
b64toab(encodedString);
}
function b64toab(b64) {
var byteString = b64DecodeUnicode(b64);
console.log(byteString);
var byteArray = new Uint8Array(byteString.length);
for(var i=0; i < byteString.length; i++) {
byteArray[i] = byteString.codePointAt(i);
}
console.log(byteArray);
window.crypto.subtle.importKey(
"pkcs8",
byteArray,
{
name: "RSA-OAEP",
hash: {name: "SHA-256"}
},
true,
["decrypt"]
).then(function(importedPrivateKey) {
console.log(importedPrivateKey);
});
}
You forgot to include the last part of PEM when you split the string in blocks of 64 characters. Just add finalString += str; to addNewLines
function addNewLines(str) {
var finalString = '';
for(var i=0; i < str.length; i++) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
finalString += str;
return finalString;
}
I have refactorized your example to see what is happening. Use the below code if you consider it useful
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
function addNewLines(str) {
var finalString = '';
for(var i=0; i < str.length; i++) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
finalString += str;
return finalString;
}
function removeLines(pem) {
var lines = pem.split('\n');
var encodedString = '';
for(var i=0; i < lines.length; i++) {
encodedString += lines[i].trim();
}
return encodedString;
}
function stringToArrayBuffer(byteString){
var byteArray = new Uint8Array(byteString.length);
for(var i=0; i < byteString.length; i++) {
byteArray[i] = byteString.codePointAt(i);
}
return byteArray;
}
function arrayBufferToString(exportedPrivateKey){
var byteArray = new Uint8Array(exportedPrivateKey);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
true,
["encrypt", "decrypt"]
).then(function(keyPair) {
window.crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey
).then(function(exportedPrivateKey) {
var privateKeyDer = arrayBufferToString(exportedPrivateKey); //pkcs#8 to DER
var privateKeyB64 = b64EncodeUnicode(privateKeyDer); //btoa(privateKeyDer);
var privateKeyPEMwithLines = addNewLines(privateKeyB64); //split PEM into 64 character strings
var privateKeyPEMwithoutLines = removeLines(privateKeyPEMwithLines); //join PEM
var privateKeyDerDecoded = b64DecodeUnicode(privateKeyPEMwithoutLines); // atob(privateKeyB64);
var privateKeyArrayBuffer = stringToArrayBuffer(privateKeyDerDecoded); //DER to arrayBuffer
window.crypto.subtle.importKey( //importKEy
"pkcs8",
privateKeyArrayBuffer,
{
name: "RSA-OAEP",
hash: {name: "SHA-256"}
},
true,
["decrypt"]
).then(function(importedPrivateKey) {
console.log(importedPrivateKey);
});
});
});
I am posting additional working code:
(NOTE: Code is without -----BEGIN PRIVATE KEY----- and ----- END PRIVATE KEY ----- base64 only)
function addNewLines(str) {
var finalString = '';
while(str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
return finalString;
}
function removeLines(str) {
return str.replace("\n", "");
}
function arrayBufferToBase64(arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCharCode(byteArray[i]);
}
var b64 = window.btoa(byteString);
return b64;
}
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;
}
window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
true,
["encrypt", "decrypt"]
).then(function(keyPair) {
window.crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey
).then(function(exportedPrivateKey) {
var pem = addNewLines(arrayBufferToBase64(exportedPrivateKey));
importKey(pem);
});
});
function importKey(b64) {
b64 = removeLines(b64);
arrayBuffer = base64ToArrayBuffer(b64);
window.crypto.subtle.importKey(
"pkcs8",
arrayBuffer,
{
name: "RSA-OAEP",
hash: {name: "SHA-256"}
},
true,
["decrypt"]
).then(function(importedPrivateKey) {
console.log(importedPrivateKey);
});
}
UPDATE:
I wrote a little crypto library that you can use for PEM converting and many more.
https://github.com/PeterBielak/OpenCrypto
Usage example:
var crypt = new OpenCrypto();
crypt.getKeyPair().then(function(keyPair) {
crypt.cryptoPrivateToPem(keyPair.privateKey).then(function(pemPrivateKey) {
console.log(pemPrivateKey);
});
});
Related
Trying to use the SubtleCrypto Web API to generate a public/private key pair, then use that keypair for encryption from a user inputting the public key, but importKey() keeps giving result as undefine. For public/private key generation, I have the following:
window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, // can be 1024, 2048 or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} // or SHA-512
},
true,
["encrypt", "decrypt"]
).then(function(keyPair) {
console.log(keyPair);
window.crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey
).then(function(exportedPrivateKey) {
// converting exported private key to PEM format
var pem = toPem(exportedPrivateKey);
console.log(pem);
}).catch(function(err) {
console.log(err);
});
window.crypto.subtle.exportKey(
"spki",
keyPair.publicKey
).then(function(exportedPublicKey) {
// converting exported private key to PEM format
var pem = toPemP(exportedPublicKey);
console.log(pem);
let resultingKey = importPublicKey(pem)
console.log('keyPairPublic: ', keyPair.publicKey)
console.log('resultingKey: ', resultingKey)
}).catch(function(err) {
console.log(err);
});
});
the toPem() and toPemP() functions are as follows:
function toPem(privateKey) {
var b64 = addNewLines(arrayBufferToBase64(privateKey));
var pem = "-----BEGIN PRIVATE KEY-----\n" + b64 + "-----END PRIVATE KEY-----";
return pem;
}
function toPemP(publicKey) {
var b64 = addNewLines(arrayBufferToBase64(publicKey));
var pem = "-----BEGIN PUBLIC KEY-----\n" + b64 + "-----END PUBLIC KEY-----";
return pem;
}
function arrayBufferToBase64(arrayBuffer) {
console.log('arrayBuffer', arrayBuffer);
var byteArray = new Uint8Array(arrayBuffer);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCharCode(byteArray[i]);
}
var b64 = window.btoa(byteString);
return b64;
}
function addNewLines(str) {
var finalString = '';
while(str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
return finalString;
}
The importKey() function is as follows:
function importPublicKey(pem) {
// base64 decode the string to get the binary data
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
let pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length)
pemContents = pemContents.replace(/\s/g, '');
console.log('pemContents:', pemContents)
let binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = str2ab(binaryDerString);
console.log('binaryDer', binaryDer)
window.crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
true,
["encrypt"]
).then(function(result) {
return result;
}).catch(function(err) {
console.log('err: ', err);
})
}
Where str2ab() is as follows:
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
All of the above functions were from the SubtleCrypto documentation. When I run the above, I get the following:
I'm not sure why it comes out as undefined, but I can notice that arrayBuffer and binaryDer don't have the exact same size. Don't know if it means anything and not sure how to fix this, any help is appreciated!
Python Code that works fine and I checked this Python code for message "a" it gives me the result "52F17E7031982DE1744A57F6EE9BD3A3"
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from OpenSSL import crypto
message = "a".encode('utf-8')
p12 = crypto.load_pkcs12(company_p12_certificate, certificate_password)
key_bytes = crypto.dump_privatekey(crypto.FILETYPE_PEM, p12.get_privatekey())
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
I implemented this using JavaScript but not getting same result. It gives me "4EB5DB7F5459E832DE3E0638A8F4C4A0" for message "a"
My JavaScript code is:
$(document).ready(function () {
function _privateKeyToPkcs8(privateKey) {
var rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
var privateKeyInfoDer = forge.asn1.toDer(privateKeyInfo).getBytes();
var privateKeyInfoDerBuff = stringToArrayBuffer(privateKeyInfoDer);
return privateKeyInfoDerBuff;
}
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(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 binary;
}
$("#file").change(function () {
var file = this.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
var contents = e.target.result;
var pkcs12Der = arrayBufferToString(contents)
var pkcs12B64 = forge.util.encode64(pkcs12Der);
var pkcs12Der = forge.util.decode64(pkcs12B64);
var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, '123456');
var privateKey
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
// this bag has a private key
if (safeBag.type === forge.pki.oids.keyBag) {
//Found plain private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
// found encrypted private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.certBag) {
// this bag has a certificate...
}
}
}
var privateKeyInfoDerBuff = _privateKeyToPkcs8(privateKey);
//Import the webcrypto key
crypto.subtle.importKey(
'pkcs8',
privateKeyInfoDerBuff,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"]).
then(function (cryptoKey) {
var sha256 = forge.md.sha256.create();
sha256.update('a'); // Message will come here
var digestToSignBuf = stringToArrayBuffer(sha256.digest().toHex());
crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, cryptoKey, digestToSignBuf)
.then(function (signature) {
var signatureB64 = forge.util.encode64(arrayBufferToString(signature))
var md5 = forge.md.md5.create();
md5.update(signatureB64);
console.log("final result", md5.digest().toHex().toUpperCase());
});
})
}
})
});
I also checked https://github.com/digitalbazaar/forge
WebCrypto implicitly generates the hash of the data during signing using the digest SHA-256 specified in the key, so explicit hashing with SHA-256 is not necessary.
Also, the generated signature is directly hashed with MD5, i.e. without prior Base64 encoding.
With these changes, the JavaScript code is (using a test key):
// Import private test key
var pkcs8pem = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----`;
var pkcs8 = convertDER(pkcs8pem);
crypto.subtle.importKey(
'pkcs8',
pkcs8,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"])
.then(function (cryptoKey) {
// Sign message
var message = 'The quick brown fox jumps over the lazy dog';
crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
cryptoKey,
stringToArrayBuffer(message))
.then(function (signature) {
// Create MD5 hash
var md5 = forge.md.md5.create();
md5.update(arrayBufferToString(signature));
console.log("Final result", md5.digest().toHex().toUpperCase()); // Final result 30FD001CFD12D0A3DF000D216C82C47E
});
});
// Helper
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(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 binary;
}
function convertDER(pem){
var pemHeader = "-----BEGIN PRIVATE KEY-----";
var pemFooter = "-----END PRIVATE KEY-----";
var pemContents = pkcs8pem.substring(pemHeader.length, pkcs8pem.length - pemFooter.length);
var binaryDerString = window.atob(pemContents);
var pkcs8 = stringToArrayBuffer(binaryDerString);
return pkcs8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/forge/0.10.0/forge.min.js"></script>
with the output:
Final result 30FD001CFD12D0A3DF000D216C82C47E
The Python code returns the same result for the same key and plaintext.
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
key_bytes = '''-----BEGIN PRIVATE KEY-----
MIIBVQI...
-----END PRIVATE KEY-----'''
message = "The quick brown fox jumps over the lazy dog".encode('utf-8')
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
print(result) # 30FD001CFD12D0A3DF000D216C82C47E
As already noted in the comments, due to the MD5 hash, verification using the public key is not possible (but apparently not intended).
Also, WebCrypto provides the SubtleCrypto.digest() function to determine a hash, so the forge library is actually not really necessary (at least for hashing).
I've not analyzed the key import in more detail, so there may be problems here as well.
I am currently implementing the RSA-OAEP encryption on Javascript and decryption at Java.
My javascript code has the following
function stringToArrayBuffer(str){
var buf = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function arrayBufferToString(str){
var byteArray = new Uint8Array(str);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
function encryptDataWithPublicKey(data, key) {
data = stringToArrayBuffer(data);
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
//label: Uint8Array([...]) //optional
},
key, //from generateKey or importKey above
data //ArrayBuffer of data you want to encrypt
);
}
var pem = Config.encryption.publicKey;
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
// base64 decode the string to get the binary data
const binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = stringToArrayBuffer(binaryDerString);
window.crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
true,
["encrypt"]
).then(function (publicKey) {
encryptDataWithPublicKey(text, publicKey).then((result) => {
var rdata = arrayBufferToString(result);
resolve(rdata);
});
}).catch(function (err) {
console.log(err);
reject(err);
});
I also have a Java function to decrypt the text. Assume "rsaOaepCipherText" is a string text.
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(), oaepParams);
return new String(cipher.doFinal(Base64.decodeBase64(rsaOaepCipherText)), "UTF-8");
However I keep getting decryption error on Java, and currently stuck at this portion, is there any error I have done on my encryption on Javascript?
Ok found it. I forgot to include btoa on the string before send to backend.
it should be
encryptDataWithPublicKey(text, publicKey).then((result) => {
var rdata = arrayBufferToString(result);
var rResult = window.btoa(rdata);
resolve(rResult);
});
I'm trying to do:
encrypt text using web.crypto,
decrypt text using AesCryptoServiceProvider
I didn't get any exceptions in my code, but the decryption doesn't match the plain text that i've encrypted
the plaintext bytes after decyption are the same, but the encoding to string doesn't work
I'm using random iv, the key and the plaintext are constant.
function cryptoSys(plaintext,KeyString){
var iVec=window.crypto.getRandomValues(new Uint8Array(16));
var encryptSuccessFunc=(encrypt)=> { AfterEncrypt(BytearrayToString(iVec),arraybufferTostring(encrypt));}
var ImportKeySuccessFunc= (keyObj)=>{
window.crypto.subtle.encrypt(
{name:"AES-CBC", iv:iVec},
keyObj,
StringToByteArray(plaintext)
).then(encryptSuccessFunc);
window.crypto.subtle.importKey(
"raw",
StringToByteArray(KeyString),
{name:"AES-CBC", length:128},
true,
["encrypt","decrypt"]
).then(ImportKeySuccessFunc);
}
After that I'm sending the iVec, ciphertext using json
function AfterEncrypt(iVec,ciphertext)
{
var plaintext="String to Encrypt";
if(iVec==null)
return;
var send2server= {"ciphertext":ciphertext,
"iVec":iVec,
"plaintext":plaintext};
var objectDataString = JSON.stringify(send2server);
sendJSONtoserver(objectDataString,"getDelayedBid");
}
I'm using the following utility functions :
function StringToByteArray(strString) {
var byteArray = new Uint8Array(strString.length);
for (var i=0; i<strString.length; i++) {
byteArray[i] = strString.charCodeAt(i);
}
return byteArray;
}
function BytearrayToString(arrayBuffer) {
var strString = "";
for (var i=0; i<arrayBuffer.byteLength; i++) {
strString += String.fromCharCode(arrayBuffer[i]);
}
return strString;
}
function arraybufferTostring(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
server side accepts the keys, and suppose to decrypt:
public string DecryptCipher(Encoding u16, string cipherText, string key,string iVec)
{
byte[] ciphertextBytes = clearZeros(u16.GetBytes(cipherText));
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 128; //minimun key length
aes.Key = clearZeros(u16.GetBytes(key));
aes.IV = clearZeros(u16.GetBytes(iVec));
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
ICryptoTransform cryptoTrans = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] plaintextBytes = cryptoTrans.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length);
cryptoTrans.Dispose();
return Convert.ToBase64String(plaintextBytes);
}
I've got this utility function as well:
private byte [] clearZeros(byte [] bytearray)
{
byte[] ans = new byte[bytearray.Length / 2];
for (int i = 0; i < bytearray.Length / 2; i++)
ans[i] = bytearray[2 * i];
return ans;
}
I'm getting the paramters from anthor function:
public ActionResult getDelayedBid([FromBody] DelayedSubmission delaySub)
{
if (delaySub.ciphertext == null)
return Json("Failure", JsonRequestBehavior.AllowGet);
Encoding u16LE = Encoding.Unicode;
String decrypetedMessageLE = new Encryption().DecryptCipher(u16LE, delaySub.ciphertext, getKeyFromDB(), delaySub.iVec);
if (decrypetedMessageLE.Equals(delaySub.plaintext) )
{
return Json("Success", JsonRequestBehavior.AllowGet);
}
return Json("Failure", JsonRequestBehavior.AllowGet);
}
I've used wrong Ecoding,
instand of
Convert.ToBase64String(plaintextBytes);
I should've used
Encoding.ASCII.GetString(plaintextBytes);
i have the problem that I get JS-Objects with fields like ... title: "действительный有効なដែលមាន... or for example containing this emote 😍. Now, due to encryption, I need to convert this information to UInt8.
The code that handles the conversion looks like this (and works properly in the general case ...)
public arrayBufferToString(arrayBuffer : Uint8Array) : string {
var str : string = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));
return str;
}
public stringToArrayBuffer(string:string) : Uint8Array {
if(typeof string === 'undefined' || string === null){
this.$log.warn('Cannot convert an undefined string');
return null;
}
var arrayBuffer : any = new ArrayBuffer(string.length);
var buffer : Uint8Array = new Uint8Array(arrayBuffer);
for (var i : number = 0, stringLength : number = string.length; i < stringLength; i++) {
buffer[i] = string.charCodeAt(i);
}
return buffer;
}
In these situations, however, I have an input like this:
"title":"действительный有効なដែលមានសុពលភាព有效有效માન્યમાבתוקף" and an output, after formatting it back to a string, like this
"title":"459AB28B5;L=K9 ¹j¶»¶ H H®¾¨Í¯®¾ÑêÕçã"
Do you have any idea why this happens? Thank you!
The following scripts encrypts and decrypts dataToEncrypt. It uses modern Encoding API.
"use strict";
var dataToEncrypt = "дей ... hopefully, it means 'day' :)";
var iv = window.crypto.getRandomValues(new Uint8Array(16));
var algorithm = {
name: "AES-CBC",
iv: iv
};
window.crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 128
},
/* extractable */ true,
/*keyUsages */ ['encrypt', 'decrypt']
).
then(function (aesKey) {
var uint16array = new TextEncoder('utf-16').encode(dataToEncrypt);
window.crypto.subtle.encrypt(algorithm, aesKey, uint16array)
.then(function (encryptedArrayBuffer) {
console.log("encrypted", ab2hexstr(encryptedArrayBuffer));
window.crypto.subtle.decrypt(algorithm, aesKey, encryptedArrayBuffer)
.then(function (result) {
console.log("decrypted", new TextDecoder('utf-16').decode(result));
})
.catch(function (err) {
console.log("Decryption failed.", err);
});
})
.catch(function (err) {
console.log("Encryption failed.", err);
});
}
).
catch(function (err) {
console.log("generateKey - failed.", err);
});
function ab2hexstr(buf) {
var byteArray = new Uint8Array(buf);
var hexString = "";
var nextHexByte;
for (var i = 0; i < byteArray.byteLength; i++) {
nextHexByte = byteArray[i].toString(16); // Integer to base 16
if (nextHexByte.length < 2) {
nextHexByte = "0" + nextHexByte; // Otherwise 10 becomes just a instead of 0a
}
hexString += nextHexByte;
}
return hexString;
}
jsFiddle for the previous script
jsFiddle for a version without Encoding API
The scripts are based on crypto.js.
Note:
I did not use TypeScript but you can add types as you like.
Resources:
https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encode
https://googlechrome.github.io/samples/encoding-api/index.html
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt