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);
});
Related
This is the algorithm for signing the data in C# using a private key from a certificate that is used from both me and the client in order to define an unique key to identify the user:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
byte[] iicSignature = privateKey.SignData(Encoding.ASCII.GetBytes("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340"), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
byte[] iic = ((HashAlgorithm)CryptoConfig,CreateFromName("MD5")).ComputeHash(iicSignature);
I then pass the private key to my Javascript using Bouncy Castle:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
var eky = DotNetUtilities.GetRsaKeyPair(privateKey);
Pkcs8Generator pkcs8Gen = new Pkcs8Generator(eky.Private);
Org.BouncyCastle.Utilities.IO.Pem.PemObject pkcs8 = pkcs8Gen.Generate();
PemWriter pemWriter = new PemWriter(new StringWriter());
pemWriter.WriteObject(pkcs8);
pemWriter.Writer.Flush();
return pemWriter.Writer.ToString();
This one is the algorithm used in Javascript:
window.crypto.subtle.importKey(
"pkcs8",
pemToArrayBuffer(pkcs8Pem), {
name: "RSASSA-PKCS1-v1_5",
hash: {
name: "SHA-256"
},
},
false, ["sign"]
)
.then(function(privateKey) {
console.log(privateKey);
// Sign: RSA with SHA256 and PKCS#1 v1.5 padding
window.crypto.subtle.sign({
name: "RSASSA-PKCS1-v1_5",
},
privateKey,
new TextEncoder().encode("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340")
)
.then(function(signature) {
var iic = md5(signature);
console.log(ab2b64(signature));
})
.catch(function(err) {
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
function removeLines(str) {
str = str.replace("\r", "");
return str.replace("\n", "");
}
function base64ToArrayBuffer(b64) {
var byteString = atob(b64);
var byteArray = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
return byteArray;
}
function pemToArrayBuffer(pem) {
var b64Lines = removeLines(pem);
var b64Prefix = b64Lines.replace('-----BEGIN PRIVATE KEY-----', '');
var b64Final = b64Prefix.replace('-----END PRIVATE KEY-----', '');
return base64ToArrayBuffer(b64Final);
}
The signatures returned are different for some reason. I need them to be the same or else it's all pointless because the client won't be authenticated.
The results are as follow:
C#:
57CF663ACBEDE6305309682BA7261412
Javascript:
c099d176dcd95c59d748d6066dcd462e
I had to convert my signature to base64 and then encode it with atob() after that i needed this md5 library to hash the data and then use .toUpperCase() to reproduce the correct result.
The complete code looks like this:
md5(atob(ab2b64(signature))).toUpperCase();
Now i get the same result from both C# and JS.
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!
What I'm trying to do is generate a keypair within javascript, and use these with encrypting in PHP and then decrypt with JS.
I'm having two problems within the code attached
It wont reload the private key from armored text block
And it wont decrypt what PHP has encrypted
Both throw the error DOMException, instead of a useful error.
So this is my code...
PHP/JAVASCRIPT
<?php
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
if ($_POST) {
$public=$_POST['public'];
$data='some text to encrypt';
$key = PublicKeyLoader::load($public);
$key = $key->withPadding(RSA::ENCRYPTION_OAEP);
$encoded=base64_encode($key->encrypt($data));
header('Content-Type: application/json');
echo json_encode(array('encrypted'=>$encoded));
exit;
}
?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script>
jQuery(document).ready(function($) {
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function importPrivateKey(pem) {
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PRIVATE KEY-----\n";
const pemFooter = "\n-----END PRIVATE 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 = str2ab(binaryDerString);
return window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: {name: "SHA-256"}
},
true,
["decrypt"]
);
}
(async() => {
let keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: {name: "SHA-256"}
},
true,
["encrypt", "decrypt"]
);
var exported=await window.crypto.subtle.exportKey("pkcs8",keyPair.privateKey);
var exportedAsString = ab2str(exported);
var exportedAsBase64 = window.btoa(exportedAsString);
var private = `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`;
var exported = await window.crypto.subtle.exportKey(
"spki",
keyPair.publicKey
);
var exportedAsString = ab2str(exported);
var exportedAsBase64 = window.btoa(exportedAsString);
var public = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
console.log(public);
console.log(private);
$.ajax({
url:window.location,
type:'POST',
data:{
public:public
},
success:function(data) {
(async() => {
console.log('*ENCRYPTED BY PHP*',data.encrypted);
// HELP!!! NEED TO BE ABLE TO RELOAD THE KEY FROM ARMORED STRING
var key=await importPrivateKey(private); // Error - Uncaught (in promise) DOMException
var buffer=str2ab(window.atob(data.encrypted));
// HELP!!! WONT DECRYPT WHAT PHP ENCODED USING THE PUBLIC KEY
var decrypted=await window.crypto.subtle.decrypt({name:"RSA-OAEP"},key,buffer);
console.log('DECRYPTED',decrypted);
})();
}
});
})();
});
</script>
The bug is in the str2ab() function, which uses a Uint16Array instead of a Uint8Array.
If this is fixed, the private key can be imported and the ciphertext generated with the PHP code can be decrypted:
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function str2ab(str) {
// Fix: Don't double the size
var buf = new ArrayBuffer(str.length);
// Fix: Apply a Uint8Array!
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function importPrivateKey(pem) {
const pemHeader = "-----BEGIN PRIVATE KEY-----\n";
const pemFooter = "\n-----END PRIVATE KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
const binaryDerString = window.atob(pemContents);
const binaryDer = str2ab(binaryDerString);
return window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: {name: "SHA-256"}
},
true,
["decrypt"]
);
}
(async function() {
// Apply the private key from key pair generated with the posted JavaScript code
var privateKey =
`-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKeQUdBu3zTX6QyfGfRWYxWWOnxd2xssTOIu6XczDByQEMfBbpQO9iM3u/Mn84zZFPFNvOKUNxcnftmrPiqUO9fBI2aAh77d2m65FBGsm4k/oUPzMNORGaDdBY4gg8FPMKo60kqBaMXAwzF8I4EUS/ot2fkBzSL0BGXT9o1NaO8bAgMBAAECgYAO2OPW8ywF86ervaFAHDN1YzVVdb+HXdqGJB/9tuE42q8R9BrHNbgrkLGvrveOoGGRrBCzhuyGubIsuVat0SqoI6qEnB9uahaIBfF5FZ7+bNW5OfkgerUUYP1S1MGFxUqINnUY1YHITmo6pUKHsiJtP7sihnCT6uEx8LqVNf1quQJBANs+VCZVUDq6eMy3E/u03HiAB8cyqLVMVQ4cLyoiWmFlnEFzZwMd20ZMjtcxICiizW3dlDvyxWYKH93irL0JyM0CQQDDp/VFsh83vKICVvM9IZHwE/Z8vZA3eTkGbWmgnr6qaxqge3FU02kUvIHHlvLmXYIt30lTq0Rn+Lz+TGV/jDeHAkBHYSaSiGojhLx5og1+gKbbEIv3vbWRuTVj76cnZ6HXXfaelIzwRdMzMw+6XgMjV8XcRCzTy7ma/Cbd3cPxk/LtAkEAwkehMVexz/KrHI+icG1JMI9iDnNdJPhmO4+hdzCqOyanBfwNiSF0Encslze4ci8f+NTjRwWlo2hGomzRzFk7OQJAPPd/o0azkg9nF+JxLiz7hF+/6MLVZgIfw04u05ANtOSVVQP4UTmJ/tNAe3OBUQVlRQAJ1m3jzUlir0ACPypC1Q==
-----END PRIVATE KEY-----`;
// Use the ciphertext generated with the PHP code
var ciphertext = 'a8gEZ6/DymB8dTGPytQPNS8QiYFuUULK+/c0vtie1l722isC0Z/jSeC2ytA6MjVUuTdq7sPuNW850gEZ2XvKujLQzl9sjJ8pcsxznBzMK8v03YJCTBr2lbfHpEEtuSLaAR2UbovXDoCyIIvOnMjqlIS3Ug2PG4hALThn/aAUwE0=';
var key = await importPrivateKey(privateKey);
var decryptedBuffer = str2ab(window.atob(ciphertext));
var decrypted = await window.crypto.subtle.decrypt(
{name:"RSA-OAEP"},
key,
decryptedBuffer
);
console.log(ab2str(decrypted)); // some text to encrypt
})();
Here, the posted JavaScript code was applied to generate an RSA key pair. The public key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnkFHQbt801+kMnxn0VmMVljp8XdsbLEziLul3MwwckBDHwW6UDvYjN7vzJ/OM2RTxTbzilDcXJ37Zqz4qlDvXwSNmgIe+3dpuuRQRrJuJP6FD8zDTkRmg3QWOIIPBTzCqOtJKgWjFwMMxfCOBFEv6Ldn5Ac0i9ARl0/aNTWjvGwIDAQAB
-----END PUBLIC KEY-----
was used to perform the encryption with the posted PHP code and the private key is applied in the code above for decryption.
My clean data is : Test12345678910
My secret key is : 12345678901234561234567890123456
When i encrypt the data i get this output : ldJbAK2rYjDnS6kWz2O+Aw==
when i decrypt it i get error
internal/crypto/cipher.js:164
const ret = this._handle.final();
^
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
my code is ;
const crypto = require('crypto');
const encrypted = "ldJbAK2rYjDnS6kWz2O+Aw==";
const key = "12345678901234561234567890123456";
let decipher = crypto.createDecipher('aes-256-cbc', key);
let decrypted = decipher.update(encrypted);
decrypted = [decrypted, decipher.final("utf-8")];
console.log(decrypted.toString());
Encrypting from C#
public static string EncryptToken(string text)
{
string confkey = "12345678901234561234567890123456";
byte[] array;
try
{
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(confkey);
aes.IV = new byte[16];
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
{
streamWriter.Write(text);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
catch
{
return string.Empty;
}
}
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);