JS SubtleCrypto RSA Encrypt and Decrypt - javascript

So I am trying to implement some methods to encrypt then decrypt some data. I don't have any experience with this, and I've tried to follow along with some posts online on how to go about this.
When i pass the encrypted 'hello' into the decrypt function, i get this:
let a = importPublicKeyAndEncrypt('hello')
CryptoKey {type: "public", extractable: true, algorithm: {…}, usages: Array(1)} W29iamVjdCBBcnJheUJ1ZmZlcl0=
importPrivateKeyAndDecrypt(a)
Promise {<pending>}
DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
at importPrivateKeyAndDecrypt (<anonymous>:26:60)
The decrypt function works correctly when i use an encrypted messages from one of the posts i saw for PKCS#8 but not when i generate my own keys.
Here is the code
What am i doing wrong?
// PEM encoded X.509 key
const publicKey = `
-----BEGIN PUBLIC KEY-----
<removed for space>
-----END PUBLIC KEY-----`;
// PEM encoded PKCS#8 key
const privateKey = `
-----BEGIN PRIVATE KEY-----
<removed for space>
-----END PRIVATE KEY-----`;
async function importPublicKeyAndEncrypt(str) {
try {
const pub = await importPublicKey(publicKey);
console.log(pub);
const encrypted = await encryptRSA(pub, new TextEncoder().encode(str));
const encryptedBase64 = window.btoa(ab2str(encrypted));
console.log(encryptedBase64.replace(/(.{64})/g, '$1\n'));
} catch (error) {
console.log(error);
}
}
async function importPrivateKeyAndDecrypt(str) {
try {
const priv = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(priv, str2ab(window.atob(str)));
console.log(decrypted);
} catch (error) {
console.log(error);
}
}
async function importPublicKey(spkiPem) {
return await window.crypto.subtle.importKey(
'spki',
getSpkiDer(spkiPem),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt']
);
}
async function importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
'pkcs8',
getPkcs8DerDecode(pkcs8Pem),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['decrypt']
);
}
async function encryptRSA(key, plaintext) {
let encrypted = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
key,
plaintext
);
return encrypted;
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
function getSpkiDer(spkiPem) {
const pemHeader = '-----BEGIN PUBLIC KEY-----';
const pemFooter = '-----END PUBLIC KEY-----';
var pemContents = spkiPem.substring(
pemHeader.length,
spkiPem.length - pemFooter.length
);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
function getPkcs8DerDecode(pkcs8Pem) {
const pemHeader = '-----BEGIN PRIVATE KEY-----';
const pemFooter = '-----END PRIVATE KEY-----';
var pemContents = pkcs8Pem.substring(
pemHeader.length,
pkcs8Pem.length - pemFooter.length
);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
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;
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}

There are only two minor flaws.
First, the return statements are missing in importPublicKeyAndEncrypt() and in importPrivateKeyAndDecrypt() (although the latter is not necessary for the current code snippet).
Also, it is necessary to wait for the promise of importPublicKeyAndEncrypt() before importPrivateKeyAndDecrypt() can be called.
With these fixes the code works:
// PEM encoded X.509 key
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunF5aDa6HCfLMMI/MZLT
5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1E
bYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQw
KtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1x
H9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4
OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4i
GwIDAQAB
-----END PUBLIC KEY-----`;
// PEM encoded PKCS#8 key
const privateKey = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8sw
wj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqW
rbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4O
F2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtL
zd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOW
uL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEq
kXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1
c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODw
czdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfy
qmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBX
mNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyF
EM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3o
bhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884Jo
RS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDs
jRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCD
JjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9
/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3
qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpM
IvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltaya
j3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFal
CbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdH
aj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIA
X4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTG
Pr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6
ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLA
BviZm5AFCQWfke4LZo5mOS10
-----END PRIVATE KEY-----`;
async function importPublicKeyAndEncrypt(str) {
try {
const pub = await importPublicKey(publicKey);
//console.log(pub);
const encrypted = await encryptRSA(pub, new TextEncoder().encode(str));
const encryptedBase64 = window.btoa(ab2str(encrypted));
//console.log(encryptedBase64.replace(/(.{64})/g, '$1\n'));
return encryptedBase64;
} catch (error) {
console.log(error);
}
}
async function importPrivateKeyAndDecrypt(str) {
try {
const priv = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(priv, str2ab(window.atob(str)));
//console.log(decrypted);
return decrypted;
} catch (error) {
console.log(error);
}
}
async function importPublicKey(spkiPem) {
return await window.crypto.subtle.importKey(
'spki',
getSpkiDer(spkiPem),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt']
);
}
async function importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
'pkcs8',
getPkcs8DerDecode(pkcs8Pem),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['decrypt']
);
}
async function encryptRSA(key, plaintext) {
let encrypted = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
key,
plaintext
);
return encrypted;
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
function getSpkiDer(spkiPem) {
const pemHeader = '-----BEGIN PUBLIC KEY-----';
const pemFooter = '-----END PUBLIC KEY-----';
var pemContents = spkiPem.substring(
pemHeader.length,
spkiPem.length - pemFooter.length
);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
function getPkcs8DerDecode(pkcs8Pem) {
const pemHeader = '-----BEGIN PRIVATE KEY-----';
const pemFooter = '-----END PRIVATE KEY-----';
var pemContents = pkcs8Pem.substring(
pemHeader.length,
pkcs8Pem.length - pemFooter.length
);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
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;
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
(async () => {
let ciphertext = await importPublicKeyAndEncrypt('hello');
console.log("Ciphertext:\n", ciphertext.replace(/(.{48})/g, '$1\n'));
let decryptedData = await importPrivateKeyAndDecrypt(ciphertext);
console.log("Decrypted data:", decryptedData);
})();

I use jsencrypt
Client side:
const publicKey = `-----BEGIN PUBLIC KEY-----
keydatakeydata
-----END PUBLIC KEY-----`;
export function encrypt(text) {
let encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
let encrypted = encrypt.encrypt(text);
return encrypted.toString();
}
NodeJs
function decryptString(ciphertext) {
const privateKey = fs.readFileSync('./rsa_512_priv.pem').toString();
const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING
}, Buffer.from(ciphertext, 'base64'));
return decrypted.toString("utf8");
}
keys were generated with
openssl genrsa -out rsa_1024_priv.pem 1024
openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem

Related

Encrypt on PHP and Decrypt on Javascript

Recently I am trying to find a way to encrypt data on PHP with a Public Key generated on javascript using window.crypto.subtle.generateKey()
async function GenerateKeys() {
let key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
true,
["encrypt", "decrypt"]
)
let pvkey = await window.crypto.subtle.exportKey(
"pkcs8",
key.privateKey
)
let pbkey = await window.crypto.subtle.exportKey(
"spki",
key.publicKey
)
let pemPvKey = `-----BEGIN PRIVATE KEY-----\n${window.btoa(ab2str(pvkey))}\n-----END PRIVATE KEY-----`;
let pemPbKey = `-----BEGIN PUBLIC KEY-----\n${window.btoa(ab2str(pbkey))}\n-----END PUBLIC KEY-----`;
return [pemPvKey, pemPbKey]
}
I then send the public key to my PHP script and generate data
$pbkey = "-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA52N7Q/kQxNrVaGCLOlD0IgrSROPWt029GTqRKMdFQSFMAZPD0V5TZPLDylbtmvLhDajKugYpjHfDGD44cXiYy1jZeVCiFas09gAGBKmgFY4Ixsl/+opF2hlPjvnugn2aPKpSLgoX9f1DpXBDEpuHJ+AVSTxL+C3uE1PgQPYy2ots15Km4W2AnV+p81UfLdafStQ40gbkUOHvzFkwizazm4q1Scjh2Fc+RR6FXy+ySp54yuRqHuMS8QTdXVMBChqs5lNwuu6V+BEryXMFbDx2fW9qCWShVTc2lq4KzvXRE/65L0sttMA7oP/WzsVW3zeNFgla8g4dxvCftwrwGviMay9N+vsa902TClR2xj7/JSzQXSW0E4Am4ZB4bCJh04f08M0nMBY6yWWV3+QpBG+9CeXHfwPrdqT2OR5N7Jq+xslCfbf4D/9itjDdvaBZP5Z6BYiOF3QzeTk+V2rfAWZdOUcuWAdP3gczQKdJM11bTxOKNCiYmBu773aDXCNFpaHDatqUgnNrVp7guOP1owOdd7qxWsplzylQUcWuhuWczs7/X/UPwT7vBTkg0pb8Ujrr+KyNmsxsJTefg1z4xcbxtgqgoDRxXu92V9iVl1HVssAM8IZdc+naEIyOjt+3OPZkaCwts38U7nYUDd96ovHjMs0hFlTxqsltwyfPFAVygLUCAwEAAQ==
-----END PUBLIC KEY-----";
openssl_public_encrypt("secret", $encrypted, $pbkey, OPENSSL_PKCS1_OAEP_PADDING );
echo base64_encode($encrypted);
Finally, I use the generated cipher on javascript by using decrypt function (the key is stored in localstorage btw in pkcs8 format)
function FormatPrivateKey(pemPvKey) {
return pemPvKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/[\r\n]/gm, "");
}
function GetKeys() {
const pvkey = localStorage.getItem("pvkey");
const pbkey = localStorage.getItem("pbkey");
return [pvkey, pbkey]
}
async function Decrypt(message) {
const keys = GetKeys();
let pemPvKey = keys[0];
const pvkey = await window.crypto.subtle.importKey(
"pkcs8",
str2ab(window.atob(FormatPrivateKey(pemPvKey))),
{
name: "RSA-OAEP",
hash: {name: "SHA-256"}
},
false,
["decrypt"]
);
return await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
pvkey,
str2ab(window.atob(message))
);
}
When I try to decrypt the data, I receive this error:
Uncaught DOMException: The operation failed for an operation-specific reason
I do not know why this happens.
PHP/OpenSSL only supports OAEP with SHA1 for both digests, see here.
So to decrypt the ciphertext generated with PHP OpenSSL, SHA-1 must be specified in Decrypt() or more precisely in importKey() instead of SHA-256.
Consequently, SHA-1 should also be specified in the key generation. However, this is not mandatory because of the export/reimport. It would be necessary if the generated private key was used directly (i.e. without reimport) for decryption, which is not the case in this scenario.
In the example below, I generated the keys with the unmodified JavaScript code and the ciphertext with the PHP code. The decryption is successful when using the described bugfix.
(async () => {
async function Decrypt(message) {
const keys = GetKeys();
let pemPvKey = keys[0];
const pvkey = await window.crypto.subtle.importKey(
"pkcs8",
str2ab(window.atob(FormatPrivateKey(pemPvKey))),
{
name: "RSA-OAEP",
hash: {name: "SHA-1"} // Fix!
},
false,
["decrypt"]
);
return await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
pvkey,
str2ab(window.atob(message))
);
}
function GetKeys() {
const pvkey = `-----BEGIN PRIVATE KEY-----
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
-----END PRIVATE KEY-----`;//localStorage.getItem("pvkey");
const pbkey = "";//localStorage.getItem("pbkey");
return [pvkey, pbkey]
}
function FormatPrivateKey(pemPvKey) {
return pemPvKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/[\r\n]/gm, "");
}
// https://stackoverflow.com/a/11058858
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;
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
var ciphertext = "Oc2XZkps7x345NtfOjFLQZPIu3TPJ6Lmws8RYdCC42RlSnI3MMUm5RNSdXV3uzDpusOYXH7kZAndbfqaak5t3hC8W7P+RWlUcDXgKrlqr3RfQmHZYKwZG3wfbgPmJMPS3+FHNG19fKISUAntCvkF625UdvPL5PPSymlDQf3VrD+jjeTXephuBpTEENwZg5lPuV2mI+QvhFzq2qaG5ij7UHCBfkTKRmWfXTS1gUUnYf1zzsWXX6THVV3PS4V6cKrDYm/hLVnU0giL2pdLRWWlyrrnPTWbSVlYSla1iheOdg+QCt9ujCkQ73aJA7nxIB711XYWxkm9VNq6sauzvaTk3QyJAabKwK8jQn0DBuvbgiaHoOhpCwOOqJWb10B7qnqxzj9u+ufmdZkWtVS++IibXXbZy2nN0P/DCT43RJHrQsUxDNfLqXp/ZnTQEjyHvGMvk6c5yL2KA3Q94wDBSiukBp8Y8Gtcuxmse+EjTbi2QE/eNqBjovxtLuU2GqjgTAgfEmgvncpa4W8cq71Der0khVopg67tIUgPhAZXJKCzz+C1Rw/L40vED7+2g5OYhMid/nyj5oFn3BwQ52feZVq2KdR14feH2bHnJnLYHTRVYBSU42LDTA+dNO8sbQ2lJyF24+y+rHx1X80Xz8YiSfJWnW4D4hpDjK7cRO9WLWQhYaE=";
console.log(ab2str(await Decrypt(ciphertext)));
})();
To use OAEP with SHA-256 on the PHP side phpseclib is an option.

Web Crypto Api fails to verify signature after converting from string

I am storing my data, including the signature in string format, and after converting the signature to and from strings following the MDN Documentation the verification process fails every time. The relevant code bits are:
const publicKey: CryptoKey = await window.crypto.subtle.importKey("spki", str2ab(window.atob(publicKeyStr)), { name: "RSA-PSS", hash: { name: "SHA-256" } }, false, ["verify"]);
const privateKey: CryptoKey = await window.crypto.subtle.importKey("pkcs8", str2ab(window.atob(privateKeyStr)), { name: "RSA-PSS", hash: { name: "SHA-256" } }, false, ["sign"])
if(privateKey && publicKey) {
try {
const sign = await window.crypto.subtle.sign({name: "RSA-PSS", saltLength: 32}, privateKey, encodedMessage);
const signature = window.btoa(String.fromCharCode.apply(null, [...new Uint8Array(sign)])); //the way said signature is stored and I can load it
const res = await window.crypto.subtle.verify({name: "RSA-PSS", saltLength: 32}, publicKey, str2ab(signature), encodedMessage);
console.log("Verifying:", signature, publicKey, res);
return res;
} catch (error) {
console.error(error);
return false;
}
}
return false;
With the str2ab(str) function being:
export function str2ab(str: string): ArrayBuffer {
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;
}
And encoded message is new TextEncoder.encode(messageString)
You store the Base64 encoded signature, but do not perform a Base64 decoding before verification, i.e. the Base64 decoding must be added. For this, in:
const res = await window.crypto.subtle.verify({name: "RSA-PSS", saltLength: 32}, publicKey, str2ab(signature), encodedMessage);
replace str2ab(signature) with b642ab(signature), where b642ab() performs the Base64 decoding:
(async () => {
var messageString = "The quick brown fox jumps over the lazy dog";
var encodedMessage = new TextEncoder().encode(messageString);
var publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunF5aDa6HCfLMMI/MZLT5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1EbYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQwKtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1xH9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGwIDAQAB";
var privateKeyStr = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8swwj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqWrbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4OF2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtLzd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOWuL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEqkXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODwczdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfyqmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBXmNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyFEM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3obhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884JoRS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDsjRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCDJjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpMIvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltayaj3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFalCbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdHaj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIAX4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTGPr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLABviZm5AFCQWfke4LZo5mOS10";
const publicKey = await window.crypto.subtle.importKey("spki", str2ab(window.atob(publicKeyStr)), { name: "RSA-PSS", hash: { name: "SHA-256" } }, false, ["verify"]);
const privateKey = await window.crypto.subtle.importKey("pkcs8", str2ab(window.atob(privateKeyStr)), { name: "RSA-PSS", hash: { name: "SHA-256" } }, false, ["sign"]);
if(privateKey && publicKey) {
const sign = await window.crypto.subtle.sign({name: "RSA-PSS", saltLength: 32}, privateKey, encodedMessage);
const signature = window.btoa(String.fromCharCode.apply(null, [...new Uint8Array(sign)])); //the way said signature is stored and I can load it
const res = await window.crypto.subtle.verify({name: "RSA-PSS", saltLength: 32}, publicKey, b642ab(signature), encodedMessage);
console.log("Verifying:", /*signature, publicKey,*/ res);
}
})();
function b642ab(base64_string){
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
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;
}
After this bug is fixed, verification is successfully performed.

RSA encryption with Java and decryption with JavaScript

I am trying to encrypt with Java (using javax.crypto.Cipher) and decrypt with JavaScript (using crypto.subtle). What I am doing is, I make the JavaScript side generate the key pair, then send the public key to the Java side by the following:
$(window).on("load", function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
crypto.subtle.generateKey({
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
).then(function ({ privateKey, publicKey }) {
crypto.subtle.exportKey("spki", publicKey).then(function (spki) {
const strPublicKey = spkiToString(spki);
// SEND THE PUBLIC KEY TO THE SERVER (JAVA)
});
});
});
function spkiToString(keydata) {
var keydataS = arrayBufferToString(keydata);
return window.btoa(keydataS);
}
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;
}
The server uses the public key for encryption:
try {
String publicKey = ""// this will come from the JS side
String message = "encrypt me"//
byte[] publicBytes = Base64.getDecoder().decode(publicKey).getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"),
PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, pubKey, oaepParams);
return Base64.getEncoder().encode(cipher.doFinal(message));
} catch (Exception e) {
e.printStacktrace();
}
return new byte[0];
The resulted value is then returned to the Javascript side, so it can decrypt the message:
const encryptedToken = "" // this will be obtained from the server
crypto.subtle.decrypt({
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
privateKey,
enc.encode(atob(encryptedToken))
).then(function (result) {
console.log("decrypted", dec.decode(result))
}).catch(function (e) {
console.log(e);
})
When the Javascript tries to decrypt, it throws a DOMException with no message (check the attached image).
What I am doing wrong? Thank you in advanced.
The problem is the enc.encode(atob(encryptedToken)) line in the decrypt() method of the last JavaScript code snippet. The UTF-8 encoding corrupts the data, preventing successful decryption. If this is changed, decryption works as shown in the following.
The JavaScript code below is essentially the same as the first code snippet from the question, with the addition of exporting the private key in PKCS8 format:
crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
).then(function ({ privateKey, publicKey }) {
crypto.subtle.exportKey("spki", publicKey).then(function (spki) {
const strPublicKey = spkiToString(spki);
console.log(strPublicKey.replace(/(.{56})/g,'$1\n'));
crypto.subtle.exportKey("pkcs8", privateKey).then(function (pkcs8) {
const strPrivateKey = spkiToString(pkcs8);
console.log(strPrivateKey.replace(/(.{56})/g,'$1\n'));
});
});
});
function spkiToString(keydata) {
var keydataS = arrayBufferToString(keydata);
return window.btoa(keydataS);
}
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;
}
This code was used to generate e.g. the following public key in X.509/SPKI format:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1F8+EvG9XP8jSXItV89QtlYy/5Z+arMegvMwsasS5IIUdr4b4eE2FGoDalaqyAxWOg/pBkzfBWAQkhuKz3i14OsBYQl1QDDm3yfmI498SsE7tZyrENCfTGrPwoCrQmEwTWOCfIBCh+mGRAUTgcsQO/g8pIFglF3QTTzlu3n0KhQIDAQAB
and private key in PKCS8 format:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=
The public key was applied in the Java code to generate the ciphertext below:
jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y=
The following JavaScript code is essentially the same as the 3rd code snippet extended by the import of the private key generated above. In addition, the ciphertext generated above is applied:
var pkcs8B64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=";
const pkcs8StrDER = atob(pkcs8B64);
const pkcs8DER = str2ab(pkcs8StrDER);
crypto.subtle.importKey(
"pkcs8",
pkcs8DER,
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["decrypt"]
).then(function(privateKey){
const dec = new TextDecoder();
const encryptedToken = "jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y=";
crypto.subtle.decrypt(
{
name: "RSA-OAEP",
},
privateKey,
str2ab(atob(encryptedToken))
).then(function (result) {
console.log("decrypted:", dec.decode(result))
}).catch(function (e) {
console.log(e);
});
});
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;
}
Instead of the UTF-8 encoding which corrupts the data, the function str2ab() is used which converts the Base64 decoded data into an ArrayBuffer.
The substitution _base64ToArrayBuffer(encryptedToken) suggested in my comment is equally possible. Unlike str2ab(), _base64ToArrayBuffer() also performs the base64 decoding.
Running the code results in the plaintext encrypt me of the Java code.

Decrypt an RSA message from browser with window.crypto.subtle APIs

I'm trying to decode an RSA 2048 bit message encoded with a public key using the corresponding private key. The environment is google chrome and I'm using the window.crypto.subtle APIs.
I generated the key couple and encoded the message using openssl tools:
# generate keys and put the private one in file private_key.pem
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# extract public key in file public_key.pem
openssl rsa -pubout -in private_key.pem -out public_key.pem
# encode message input.txt using the public key
openssl rsautl -encrypt -oaep -inkey public_key.pem -pubin -in input.txt -out msg_rsa.enc
# convert the encoded msg in base 64 format
base64 msg_rsa.enc > msg_rsa_64.enc
This is the javascript code I'm using to decode the message:
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;
}
async function importPrivateKey(pem) {
pem = pem.replace( /[\r\n]+/gm, "" );
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----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: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["decrypt"]
);
}
async function decryptRSA(_key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
_key,
ciphertext
);
const dec = new TextDecoder();
return dec.decode(decrypted);
}
const fromBase64 = base64String => Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
window.onload = init;
async function init() {
const privateKey = '-----BEGIN PRIVATE KEY-----\
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3jmTi3O1k2YXs\
AM6nNTTIzDq5YWkxYrYb6cpO9eYuzmphgRnVDR6a1YWRXMoCuCfuNXcDGywzudRn\
bBMw0FHKLUqCttVHGpZYu0+0tRR10ubxiz/xnd/aCmRYHcmUNn8Qdh3KU59A9HK5\
HhYFf1vhK8r3fkoO4CjoGo1ROzXyMybUSy+4mSNscUtt5LwrVn48vXvG5i5B4DRT\
nM4cINmutEzA2s5cDt+dzU4Py71fKBRDRIGGn0vdVSoZKbWuhm5WewyRewCk7HFc\
PALCi5/1A7VKDAHUC4FlXmuG2+wzdchEyxMj6oLR7+BkKFQaTmuMM/22cGBjVTVt\
pSr3iDovAgMBAAECggEBAIuTQW+oovNu3IDq1DkdIjgV5AmW4tBkySlMi0OjhBbP\
auEdtDDnOwBtoJU6Q3nx4psmGItKHEBw6+yAp88UeT0NV30x3delhfGO7Trx/s7h\
Qi8lvcfSTqeUA11luSR0lAZGaryw/YX820eccw5XG9yK2ll7tIC/PxvPJOpB5fF2\
XGxGrionTjHDzXJ1OWX0i0aZlNNufInJAHhlt7aT3GiQMKcQs+AUb/+bWxI3Hln8\
KcL13EUlD4pJW8vtTK3gCnQNKKMoPB5Ugqe5BrU8ElkBz+zSKDnVwt5bgjrlucYz\
rKJxWr6/qTRZkzmvkhaJeNzzepfwkFsQ/eHcxYrtuDECgYEA8OXkQ2SqYDZwizCd\
SuVkx2zHm3zXYRSlRtnXYoUdJyTyuZ4k2GvXBrlwRsOJ14emVkHKyR5enmNjwrW5\
dcD2tbBzavcqOYAMiHcKklcS/gWgPx0X5QFHU33vr8u51BQWCz75lxddWNKxVAN/\
cUTugONtS4+EP4dSZhuxHt6RscsCgYEAwxA9QmZrI54hjMkIyqwmviRmIk42S5kk\
OxbhxBbt5qVueLRB092JyGmCR2kYiqdHCYkGFDOE4kni6Bsszq5CSJvhiATFeZeX\
ldFQeZqAiRY2rAd7xD1upMug/cK3ODA8k3k/e72CtyxtBTR01q29SnPx5p/57MrI\
3ogddHlGvK0CgYEA3VqhELwjQh1D9OJK5lM683SlRd7FGdOauyvYmhKu4xU0ZBNI\
0ATnpKoo3R04P+/JjGEQMRXS4794H6ZUMDuLdxAYPiW3ivZ6jbq04BtavEf3I4dc\
OXWfULzbzbFpo9KBHvxS4974S3Hut8AvDqnEbnKML25EmwuBT4oKis8BGVkCgYEA\
nusPDZbFeNou+TUbzYrdcZHUB+TyhTq58s4clxYbMgrbaslozAQ0aavT8Pvle6j2\
zgTth+3FOFr72x+wrJ358I/W+Wrxu7NOU0eZqci/KXCIkDT0l5d5GhewDK3jeYqK\
/5cLqnNmGHfARjpLak9X5V162erBwjIf3nTEkozvnW0CgYB6L1CX3DkTFH3OBcJe\
SvV18RDUfNI8MpUKcpofmwwvgER3GrehSZHRVxVwNbnJOqbh/oiwmmNJieKrFsk9\
EzCRBVWdZByHHYW2js+gCrAp+ghnl1QEAeCU7YTxCJ2fZIAmfB9X4u/7ARtVxnZY\
mOWlm65KUYr5lf2Ws5plL4pCRA==\
-----END PRIVATE KEY-----';
const ciphertext = 'F6/NwENdUZSl+vrgpWVkyWPQuYaTGDNZPIvj4KmIRHVx4qybxN24LPIgk0Rl84KHcLFadZWCjNpM\
vg3l826OaKZAtwvIp9IxVrMbvtNOymY6A1koKvC9ema92SR4DC9hmTtMxhUB6r3XgACtRLFqMfg+\
zYSHfFqQEGJg3yZ43hfzIq/gCfHPk5sZXASq5WY5b9yd4gRonn5D4OCD6xna/r5ovHfrpO/Fwe8N\
eeY2gqTAdtzvtmOw/HLQhGANejpJYr1IriQbepM7jLjBkJX+uCn38O1MxpQb7s5RXTvGvoEoofWV\
Cq8gNFhgnVFuurdZUiY0bn58UwaVFdwzEfDSUQ==';
try {
const key = await importPrivateKey(privateKey);
const decoded = await decryptRSA(key, fromBase64(ciphertext));
console.log(decoded);
} catch(error) {
console.log(error);
}
}
Running the code, I got an exception in window.crypto.subtle.decrypt with the rather useless message "DOMException".
What am I doing wrong?
Thanks
There's only one flaw: The posted code currently uses OAEP with SHA256. The ciphertext can be decrypted with the posted key if OAEP with SHA1 is applied as padding.
In addition, the function fromBase64 can be used to Base64 decode the key into a TypedArray, so the function str2ab is actually not needed (but of course this is not an error, just redundant).
const fromBase64 = base64String => Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
const getPkcs8Der = pkcs8Pem => {
pkcs8Pem = pkcs8Pem.replace( /[\r\n]+/gm, "" );
const pkcs8PemHeader = "-----BEGIN PRIVATE KEY-----";
const pkcs8PemFooter = "-----END PRIVATE KEY-----";
pkcs8Pem = pkcs8Pem.substring(pkcs8PemHeader.length, pkcs8Pem.length - pkcs8PemFooter.length);
return fromBase64(pkcs8Pem);
}
async function importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
"pkcs8",
getPkcs8Der(pkcs8Pem),
{
name: "RSA-OAEP",
hash: "SHA-1", // Replace SHA-256 with SHA-1
},
true,
["decrypt"]
);
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
key,
ciphertext
);
const dec = new TextDecoder();
return dec.decode(decrypted);
}
window.onload = init;
async function init() {
const privateKey =
'-----BEGIN PRIVATE KEY-----\
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3jmTi3O1k2YXs\
AM6nNTTIzDq5YWkxYrYb6cpO9eYuzmphgRnVDR6a1YWRXMoCuCfuNXcDGywzudRn\
bBMw0FHKLUqCttVHGpZYu0+0tRR10ubxiz/xnd/aCmRYHcmUNn8Qdh3KU59A9HK5\
HhYFf1vhK8r3fkoO4CjoGo1ROzXyMybUSy+4mSNscUtt5LwrVn48vXvG5i5B4DRT\
nM4cINmutEzA2s5cDt+dzU4Py71fKBRDRIGGn0vdVSoZKbWuhm5WewyRewCk7HFc\
PALCi5/1A7VKDAHUC4FlXmuG2+wzdchEyxMj6oLR7+BkKFQaTmuMM/22cGBjVTVt\
pSr3iDovAgMBAAECggEBAIuTQW+oovNu3IDq1DkdIjgV5AmW4tBkySlMi0OjhBbP\
auEdtDDnOwBtoJU6Q3nx4psmGItKHEBw6+yAp88UeT0NV30x3delhfGO7Trx/s7h\
Qi8lvcfSTqeUA11luSR0lAZGaryw/YX820eccw5XG9yK2ll7tIC/PxvPJOpB5fF2\
XGxGrionTjHDzXJ1OWX0i0aZlNNufInJAHhlt7aT3GiQMKcQs+AUb/+bWxI3Hln8\
KcL13EUlD4pJW8vtTK3gCnQNKKMoPB5Ugqe5BrU8ElkBz+zSKDnVwt5bgjrlucYz\
rKJxWr6/qTRZkzmvkhaJeNzzepfwkFsQ/eHcxYrtuDECgYEA8OXkQ2SqYDZwizCd\
SuVkx2zHm3zXYRSlRtnXYoUdJyTyuZ4k2GvXBrlwRsOJ14emVkHKyR5enmNjwrW5\
dcD2tbBzavcqOYAMiHcKklcS/gWgPx0X5QFHU33vr8u51BQWCz75lxddWNKxVAN/\
cUTugONtS4+EP4dSZhuxHt6RscsCgYEAwxA9QmZrI54hjMkIyqwmviRmIk42S5kk\
OxbhxBbt5qVueLRB092JyGmCR2kYiqdHCYkGFDOE4kni6Bsszq5CSJvhiATFeZeX\
ldFQeZqAiRY2rAd7xD1upMug/cK3ODA8k3k/e72CtyxtBTR01q29SnPx5p/57MrI\
3ogddHlGvK0CgYEA3VqhELwjQh1D9OJK5lM683SlRd7FGdOauyvYmhKu4xU0ZBNI\
0ATnpKoo3R04P+/JjGEQMRXS4794H6ZUMDuLdxAYPiW3ivZ6jbq04BtavEf3I4dc\
OXWfULzbzbFpo9KBHvxS4974S3Hut8AvDqnEbnKML25EmwuBT4oKis8BGVkCgYEA\
nusPDZbFeNou+TUbzYrdcZHUB+TyhTq58s4clxYbMgrbaslozAQ0aavT8Pvle6j2\
zgTth+3FOFr72x+wrJ358I/W+Wrxu7NOU0eZqci/KXCIkDT0l5d5GhewDK3jeYqK\
/5cLqnNmGHfARjpLak9X5V162erBwjIf3nTEkozvnW0CgYB6L1CX3DkTFH3OBcJe\
SvV18RDUfNI8MpUKcpofmwwvgER3GrehSZHRVxVwNbnJOqbh/oiwmmNJieKrFsk9\
EzCRBVWdZByHHYW2js+gCrAp+ghnl1QEAeCU7YTxCJ2fZIAmfB9X4u/7ARtVxnZY\
mOWlm65KUYr5lf2Ws5plL4pCRA==\
-----END PRIVATE KEY-----';
const ciphertext =
'F6/NwENdUZSl+vrgpWVkyWPQuYaTGDNZPIvj4KmIRHVx4qybxN24LPIgk0Rl84KHcLFadZWCjNpM\
vg3l826OaKZAtwvIp9IxVrMbvtNOymY6A1koKvC9ema92SR4DC9hmTtMxhUB6r3XgACtRLFqMfg+\
zYSHfFqQEGJg3yZ43hfzIq/gCfHPk5sZXASq5WY5b9yd4gRonn5D4OCD6xna/r5ovHfrpO/Fwe8N\
eeY2gqTAdtzvtmOw/HLQhGANejpJYr1IriQbepM7jLjBkJX+uCn38O1MxpQb7s5RXTvGvoEoofWV\
Cq8gNFhgnVFuurdZUiY0bn58UwaVFdwzEfDSUQ==';
try {
const key = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(key, fromBase64(ciphertext));
console.log(decrypted);
} catch(error) {
console.log(error);
}
}

Encrypt By javascript and decrypt by Java

I use AES encryption in my react-native app as below
import CryptoJS from 'crypto-js' ;
encryptFun() {
var data = "123456";
var key = CryptoJS.enc.Latin1.parse('1234567812345678');
var iv = CryptoJS.enc.Latin1.parse('1234567812345678');
var encrypted = CryptoJS.AES.encrypt(
data,
key,
{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.ZeroPadding
});
console.log('encrypted: ' + encrypted) ;
var decrypted = CryptoJS.AES.decrypt(encrypted,key,{iv:iv,padding:CryptoJS.pad.ZeroPadding});
console.log('decrypted: '+decrypted.toString(CryptoJS.enc.Utf8));
}
Out come= encrypted: aK7+UX24ttBgfTnAndz9aQ==
following is the code I use in my backend using java for get the decrypt
public static String desEncrypt() throws Exception {
try
{
String data = "aK7+UX24ttBgfTnAndz9aQ==" ;
String key = "1234567812345678";
String iv = "1234567812345678";
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
output= decryted : = 123456[][][][][][][][][][]
Iam getting out put as 16 bit as above.
what I want is out put should come as only 123456.
I Suggest you use java.util.Base64 for decoding. The following worked out correctly. I would also suggest using trim in return originalString and see if it works out.
public class Decrypt {
public static void main(String[] args) {
try
{
String data = "aK7+UX24ttBgfTnAndz9aQ==" ;
String key = "1234567812345678";
String iv = "1234567812345678";
Decoder decoder = Base64.getDecoder();
byte[] encrypted1 = decoder.decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
System.out.println(originalString.trim());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
As you padded with zeros in the JS you can't decrypt with NoPadding, solutions would be to operate on the array or the String
trim the array
int i = original .length - 1;
while (i >= 0 && original [i] == 0) {
--i;
}
return new String(Arrays.copyOf(original , i + 1));
trim the String
return new String(original).trim();
remove manually the zero values
return new String(original).replace("\0", "");
Or, as it seems there is no ZeroPAdding in Java implemented (Cipher Documentation), take a look at How Encrypt with AES CBC Zero Padding in Javascript and decrypt with Java that suggests to use CryptoJS.pad.Pkcs
Controller part to generate RSA key ( public & private )
package com.secure.encryption.decryption.controller;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.secure.encryption.decryption.rsa.keystore.KeyStore;
import com.secure.encryption.decryption.rsa.service.RSAKeyPairGenerator;
import com.secure.encryption.decryption.util.AesUtil;
#RestController
public class EncryptDecryptController {
#Autowired
RSAKeyPairGenerator rsa;
#Autowired
KeyStore keystore;
#CrossOrigin
#GetMapping(value = "get/rsa/public")
public Map<String, Object> generateRSA_PUBLIC_PRIVATE_KEY()
{
Map<String, Object> response = new HashMap<>();
/**
* Below function creates rsa public & private key
* While this api only share PUBLICKEY
* Whereas PRIVATEKEY is stored in bean/model
*/
String public_KEY = null;
public_KEY = rsa.KeyPairGenerator();
response.put("public", public_KEY);
return response;
}
#CrossOrigin
#PostMapping(value = "/decrypt/AES_encyptedKEY/with/RSA_privateKEY/decryptdata/with/aes")
public Map<String, Object> decrypt(#RequestBody Map<String, Object> req) throws UnsupportedEncodingException
{
/**
* First decrypt AES-Key which is encrypted with RSA Public Key
* Use RSA privateKey for decryption
*/
String Decrypted_AES_Key = rsa.decrypt(req.get("phrase").toString(),keystore.getPrivateKey());
byte[] decoded = Base64.getDecoder().decode(req.get("data").toString());
String encryptedAES_Data = new String(decoded, StandardCharsets.UTF_8);
/**
* Decode data to base 64
* Use AES key to decrypt the data
*/
AesUtil aesUtil = new AesUtil(128, 1000);
String decryptedAES_Data = aesUtil.decrypt(
req.get("salt").toString(),
req.get("iv").toString(),
Decrypted_AES_Key,
encryptedAES_Data);
/**
* Map actual data as response
*/
req.put("data", decryptedAES_Data);
return req;
}
#CrossOrigin
#GetMapping(value = "/decryptfrom/backend/aes/plain/decrypt/frontend")
public Map<String, Object> sendAESencryptedData()
{
/**
* Generate random key
* Encrypt data using same
* pass key to UI
* Decrypt using same key, iv and salt
*/
Map<String, Object> response = new HashMap<>();
int i = (int) (new Date().getTime()/1000);
//String iv = generateIv().toString();
AesUtil aesUtil = new AesUtil(128, 1000);
String phrase = String.valueOf(i);//"my secret key 123";
//String salt = new String(generateSalt(32));
String iv = "bb6a69ace7a11a38fba164238e000c7c";
String salt = "6c3674b6469467ab0b9f2b57ce36e78d";
String encryptedAES_Data =
Base64.getEncoder().encodeToString(
aesUtil.encrypt(
salt,
iv,
phrase,
"ganesha")
);
response.put("data", encryptedAES_Data);
response.put("salt", salt);
response.put("iv", iv);
response.put("key", phrase);
return response;
}
/*
public IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
private byte[] generateSalt(int size) {
try {
byte[] salt = new byte[size];
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
rand.nextBytes(salt);
return salt;
} catch (Exception e) {
System.err.println(e);
}
return null;
}
*/
}
Service to generate and RSA random Key
package com.secure.encryption.decryption.rsa.service;
public interface RSAKeyPairGenerator {
public String KeyPairGenerator();
public byte[] encrypt(String data, String publicKey);
public String decrypt(String data, String base64PrivateKey);
}
Implementation Class
package com.secure.encryption.decryption.rsa.service;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.secure.encryption.decryption.rsa.keystore.KeyStore;
#Service
public class RSAKeyPairGeneratorImpl implements RSAKeyPairGenerator{
#Autowired
KeyStore keystore;
#Override
public String KeyPairGenerator() {
Map<String, Object> keypair = new HashMap<String, Object>();
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair pair = keyGen.generateKeyPair();
final String privatestring = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());
final String publicstring = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
keystore.setPrivateKey(privatestring);
return publicstring;
} catch (Exception e) {
System.err.println(e);
}
return null;
}
private static PublicKey getPublicKey(String base64PublicKey){
PublicKey publicKey = null;
try{
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.getBytes()));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return publicKey;
}
private static PrivateKey getPrivateKey(String base64PrivateKey){
PrivateKey privateKey = null;
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey.getBytes()));
KeyFactory keyFactory = null;
try {
keyFactory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
privateKey = keyFactory.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return privateKey;
}
public byte[] encrypt(String data, String publicKey) {//throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
try {
//AES/CBC/PKCS5Padding
//RSA/ECB/PKCS1Padding
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
return cipher.doFinal(data.getBytes());
} catch (Exception e) {
System.err.println(e);
}
return null;
}
private static String decrypt(byte[] data, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data));
}
public String decrypt(String data, String base64PrivateKey) {//throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
try {
return decrypt(Base64.getDecoder().decode(data.getBytes()), getPrivateKey(base64PrivateKey));
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
AesUtil class
package com.secure.encryption.decryption.util;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.tomcat.util.codec.binary.Base64;
public class AesUtil {
private final int keySize;
private final int iterationCount;
private final Cipher cipher;
public AesUtil(int keySize, int iterationCount) {
this.keySize = keySize;
this.iterationCount = iterationCount;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw fail(e);
}
}
public byte[] encrypt(String salt, String iv, String passphrase, String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes("UTF-8"));
System.out.println(encrypted);
return encrypted;//Base64.getEncoder().encodeToString(encrypted);
//String s = Base64.getEncoder().encodeToString(encrypted);
}
catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext));
return new String(decrypted, "UTF-8");
}
catch (UnsupportedEncodingException e) {
return null;
}catch (Exception e){
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
return cipher.doFinal(bytes);
}
catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
return null;
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
return null;
}
}
public static byte[] base64(String str) {
return Base64.decodeBase64(str);
}
public static byte[] hex(String str) {
try {
return Hex.decodeHex(str.toCharArray());
}
catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
private IllegalStateException fail(Exception e) {
return null;
}
}
Steps followed
Consume the RSA key
Generate random AES (key, iv, salt) - using crypto.js
Use AES and encrypt vulnerable data
Use RSA public key for encrypting AES key
Send encrypted data & key over network
Service consumes data decrypt key to AES
Use AES key to decrypt data
Send the data back to front end
Html page for reference
<!DOCTYPE html>
<html>
<body>
<script src="jquery.min.js"></script>
<script src="jsencrypt.min.js"></script>
<script type="text/javascript" src="crypto-js.min.js"></script>
<script type="text/javascript" src="aes.js"></script>
<script type="text/javascript">
const payloadsample = {
"addressLine1": "301,Kamala Mills Compound",
"addressLine2": "Gr Flr, Tulsi Pipe Rd, Lower Parel ",
"addressLine4": "Mumbai, Maharashtra",
"zipcode": 400071
};
/**
Step 1 ) - get data
**/
/**
Step 2 ) - get RSA pub Key
**/
function hybridEncryption()
{
$.ajax({
type: 'GET',//post
url: 'http://localhost:1818/get/rsa/public',
success: function(res) {
let RSAEncrypt = new JSEncrypt();
RSAEncrypt.setPublicKey(res.public);//set RSA public key
const key = Math.random().toString(36).slice(2);//Generate random AES key
console.log("key ", key);
var iv = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
var salt = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
var aesUtil = new AesUtil(128, 1000);
debugger
console.log(key)
var data = JSON.stringify({ payloadsample });
var ciphertext = aesUtil.encrypt(salt, iv, key, data);
/**
Step 3 ) - generate key
**/
senData(RSAEncrypt, iv, salt, key, btoa(ciphertext))
},
error:function(e) {
console.error(e);
},
contentType: "application/json",
dataType: 'json'
});
}
function senData(RSAEncrypt, iv, salt, key, base64Content)
{
const payload = {
"phrase":RSAEncrypt.encrypt(key),//encrypt with RSA
"data":base64Content,
"iv":iv,
"salt":salt
}
console.log("sending : ", payload);
$.ajax({
type: 'POST',
url: 'http://localhost:1818/decrypt/AES_encyptedKEY/with/RSA_privateKEY/decryptdata/with/aes',
data: JSON.stringify (payload), // or JSON.stringify ({name: 'jonas'}),
success: function(data) {
console.log(data);
},
error:function(e) {
console.error(e);
},
contentType: "application/json",
dataType: 'json'
});
}
hybridEncryption();
/**
* Incase of Backend encryption to Front end Decryption
* decryptBE() - will get AES encrypted data with associated data
* */
function decryptBE()
{
$.ajax({
type: 'GET',//post
url: 'http://localhost:1818/decryptfrom/backend/aes/plain/decrypt/frontend',
success: function(res) {
debugger
var aesUtil = new AesUtil(128, 1000);
var ciphertext = aesUtil.decrypt(res.salt, res.iv, res.key, res.data);
console.log(ciphertext);
},
error:function(e) {
console.error(e);
},
contentType: "application/json",
dataType: 'json'
});
}
</script>
</body>
</html>
This is a working example using reference
Other lib used is aes.js
var AesUtil = function(keySize, iterationCount) {
this.keySize = keySize / 32;
this.iterationCount = iterationCount;
};
AesUtil.prototype.generateKey = function(salt, passPhrase) {
var key = CryptoJS.PBKDF2(
passPhrase,
CryptoJS.enc.Hex.parse(salt),
{ keySize: this.keySize, iterations: this.iterationCount });
return key;
}
AesUtil.prototype.encrypt = function(salt, iv, passPhrase, plainText) {
var key = this.generateKey(salt, passPhrase);
var encrypted = CryptoJS.AES.encrypt(
plainText,
key,
{ iv: CryptoJS.enc.Hex.parse(iv) });
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
AesUtil.prototype.decrypt = function(salt, iv, passPhrase, cipherText) {
var key = this.generateKey(salt, passPhrase);
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(cipherText)
});
var decrypted = CryptoJS.AES.decrypt(
cipherParams,
key,
{ iv: CryptoJS.enc.Hex.parse(iv) });
return decrypted.toString(CryptoJS.enc.Utf8);
}
rest are
JSEncrypt v2.3.1 https://npmcdn.com/jsencrypt#2.3.1/LICENSE.txt
and finally crypto-js.min.js
Thank you i hope this would be usefull

Categories