I'm trying to develop some very simple code to encrypt and decrypt some simple text. The problem appears to be that when running the code the .final() of my createDecipherIV causes that error in the title to be produced.
I've attempted to toy with the various encoding (Binary vs Hex, base 64, etc.)
node: '10.15.3'
openssl: '1.1.0j'
This is an electron project, though I don't know what impact that might have
const crypto = require('crypto');
let sessionKey = crypto.randomBytes(256/8).toString('hex');
class encryption {
constructor() {
this.encryptionOptions = {
algorithm: 'aes-256-cbc',
iv: crypto.randomBytes(16),
key: String,
}
}
encryptMem(memItem){
this.encryptionOptions['key'] = Buffer.from(sessionKey,'hex');
var cipher = crypto.createCipher(this.encryptionOptions['algorithm'], this.encryptionOptions['key'], this.encryptionOptions['iv']);
var cipherText = cipher.update(memItem,'utf8','hex');
cipherText += cipher.final('hex');
return this.encryptionOptions['iv'].toString('hex') + cipherText;
}
decryptMem(memObject){
this.encryptionOptions['key'] = Buffer.from(sessionKey,'hex');
var _iv = Buffer.from(memObject.slice(0,32),'hex')
var _data = memObject.slice(32)
var _decode = crypto.createDecipheriv(this.encryptionOptions['algorithm'], this.encryptionOptions['key'], _iv);
var _decoded = _decode.update(_data,'hex','utf8');
_decoded += _decode.final('utf8')
return _decoded;
}
}
The sample code
e = new encryption
encryption {encryptionOptions: {…}}
val = e.encryptMem("test")
"adcd1f5876ca02a4420b61df5dfdaa9be3080108020df42dfc630951ffabe0ac"
e.decryptMem(val)
\lib\encrypt.js:25
internal/crypto/cipher.js:172 Uncaught Error: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT
at Decipheriv.final (internal/crypto/cipher.js:172)
at encryption.decryptMem (\lib\encrypt.js:26)
at <anonymous>:1:3
final # internal/crypto/cipher.js:172
decryptMem # \lib\encrypt.js:26
(anonymous) # VM433:1
Where as it should simply return "test
EDIT As pointed out in the answer, createCipher needed to be changed to createCipheriv
The following is some shortened code that should also correctly create a new iv with every invocation of encryption, rather then with simply the invocation of the class
const crypto = require('crypto');
let sessionKey = crypto.randomBytes(256/8).toString('hex');
class encryption {
constructor(key = null){
this.algorithm = 'aes-256-cbc';
this.key = key || Buffer.from(sessionKey,'hex');
}
encrypt(plainText){
var iv = crypto.randomBytes(16);
var cipher = crypto.createCipheriv(this.algorithm,this.key,iv);
var cipherText = cipher.update(plainText,'utf8','hex') + cipher.final('hex');
return iv.toString('hex') + cipherText;
}
decrypt(cipherText){
var iv = Buffer.from(cipherText.slice(0,32),'hex');
var data = cipherText.slice(32);
var decode = crypto.createDecipheriv(this.algorithm,this.key,iv);
var decoded = decode.update(data,'hex','utf8') + decode.final('utf8');
return decoded;
}
}
crypto.createCipher
Your block mode (CBC) requires an IV, but you're using createCipher instead of createCipheriv. I assume this is a typo since you're using createDecipheriv.
Related
I am trying to encrypt and decrypt values using node inbuild module crypto. I have followed this tutorial to encrypt the data. They haven't to gave any sample code to decrypt. When I try to use other tutorial code to decrypt the data. It not working out. Please help me out,
Code
const crypto = require('crypto');
// Difining algorithm
const algorithm = 'aes-256-cbc';
// Defining key
const key = crypto.randomBytes(32);
// Defining iv
const iv = crypto.randomBytes(16);
// An encrypt function
function encrypt(text) {
// Creating Cipheriv with its parameter
let cipher = crypto.createCipheriv(
'aes-256-cbc', Buffer.from(key), iv);
// Updating text
let encrypted = cipher.update(text);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
// Returning iv and encrypted data
return encrypted.toString('hex');
}
var op = encrypt("Hi Hello"); //c9103b8439f8f1412e7c98cef5fa09a1
Since you havent provided the code for decryption, Cant help you what is actually wrong you doing, apart from that you can do this to get decrypted code:
const crypto = require('crypto')
// Defining key
const key = crypto.randomBytes(32)
// Defining iv
const iv = crypto.randomBytes(16)
// An encrypt function
function encrypt(text) {
// Creating Cipheriv with its parameter
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv)
// Updating text
let encrypted = cipher.update(text)
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()])
// Returning iv and encrypted data
return encrypted.toString('hex')
}
var op = encrypt('Hi Hello')
console.log(op)
function decrypt(data) {
// Creating Decipheriv with its parameter
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv)
// Updating text
const decryptedText = decipher.update(data, 'hex', 'utf8')
const finalText = decryptedText + decipher.final('utf8')
return finalText
}
var decrptedData = decrypt(op)
console.log(decrptedData)
I am using the inbuilt crypto module, and been frustrated for many hours trying to figure out why decipher.update returns a function and not the deciphered text itself.
code:
const file = path.join(__dirname, '../secret.txt');
const fileIV = path.join(__dirname, '../iv.txt');
const at = path.join(__dirname, '../at.txt')
var secret = fs.readFileSync(file, 'utf-8');
const algorithm = 'aes-256-gcm';
var text = 'default'
var encrypted = secret;
const iv = crypto.randomBytes(16);
encrypt(plainText, key, iv) {
const cipher = crypto.createCipheriv(algorithm, key, iv);
return { encrypted: Buffer.concat([cipher.update(plainText), cipher.final()]), authTag: cipher.getAuthTag() }
}
decrypt(encrypted, key, iv, authTag) {
const decipher = crypto.createDecipheriv(algorithm, key, iv).setAuthTag(authTag);
console.log('this worked decrypt');
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}
SignUp(pass)
{
console.log(pass);
var pair = ec.genKeyPair();
text = pair.getPrivate.toString('hex');
const key = crypto.scryptSync(pass, 'baethrowssalt', 32);
console.log(`The key is:${key}`);
const {encrypted, authTag} = this.encrypt(text, key, iv);
console.log('encrypted: ',encrypted.toString('hex'));
const decrypted = this.decrypt(encrypted, key, iv, authTag);
console.log('Decrypted:', decrypted.toString('utf-8'));
return console.log(`Close and reopen your app to integrate your wallet securely`);
}
in console it prints this when I print out the decrypted result of the private key I initially tried encrypting with scrypt:
Decrypted: function getPrivate(enc) {
if (enc === 'hex')
return this.priv.toString(16, 2);
else
return this.priv;
}
why is
decrypt(encrypted, key, iv, authTag) {
const decipher = crypto.createDecipheriv(algorithm, key, iv).setAuthTag(authTag);
console.log('this worked decrypt');
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}
not giving me the text in its deciphered form? Additionally, how can I obtain it since I am clearly doing something wrong. Any help would really be appreciated.
The result of the decryption is exactly the same as the plaintext you encrypted!
You can easily verify this by outputting the plaintext, i.e. the contents of text, in SignUp() in the console before encrypting it:
var text = pair.getPrivate.toString('hex');
console.log('Initial plaintext:', text); // Initial plaintext: function getPrivate(enc) {...
The reason for the unexpected content of text is that you simply forgot the pair of parentheses after getPrivate, it should be:
var text = pair.getPrivate().toString('hex');
console.log('Initial plaintext:', text); // Initial plaintext: <the hex encoded private key>
Then the decryption provides the expected result.
It is probably because "decrypted.toString('utf-8')" is not executing the function but turning it into a string to show in the console...
I believe you have to do something like:
let decryptedResult = decrypted.toString('utf-8'); console.log('Decrypted:', decryptedResult.toString('utf-8'));
or but not sure
console.log('Decrypted:', (decrypted).toString('utf-8'));
I want to decrypt a message on the client-side(react.js) using Web Crypto API which is encrypted on the back-end(node.js), however I ran into a weird problem and don't have any idea what is wrong(I also checked this)
node.js
function encrypt(message){
const KEY = crypto.randomBytes(32)
const IV = crypto.randomBytes(16)
const ALGORITHM = 'aes-256-gcm';
const cipher = crypto.createCipheriv(ALGORITHM, KEY, IV);
let encrypted = cipher.update(message, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag()
let output = {
encrypted,
KEY: KEY.toString('hex'),
IV: KEY.toString('hex'),
TAG: tag.toString('hex'),
}
return output;
}
react.js
function decrypt() {
let KEY = hexStringToArrayBuffer(data.KEY);
let IV = hexStringToArrayBuffer(data.IV);
let encrypted = hexStringToArrayBuffer(data.encrypted);
let TAG = hexStringToArrayBuffer(data.TAG);
window.crypto.subtle.importKey('raw', KEY, 'AES-GCM', true, ['decrypt']).then((importedKey)=>{
window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: IV,
},
importedKey,
encrypted
).then((plaintext)=>{
console.log('plainText: ', plaintext);
})
})
function hexStringToArrayBuffer(hexString) {
hexString = hexString.replace(/^0x/, '');
if (hexString.length % 2 != 0) {
console.log('WARNING: expecting an even number of characters in the hexString');
}
var bad = hexString.match(/[G-Z\s]/i);
if (bad) {
console.log('WARNING: found non-hex characters', bad);
}
var pairs = hexString.match(/[\dA-F]{2}/gi);
var integers = pairs.map(function(s) {
return parseInt(s, 16);
});
var array = new Uint8Array(integers);
return array.buffer;
}
Encryption in back-end is done without any error, however when want to decrypt the message on the client-side, the browser(chrome) gives this error: DOMException: The provided data is too small and when I run the program on firefox browser it gives me this error: DOMException: The operation failed for an operation-specific reason. It's so unclear!!
By the way what's the usage of athentication tag in AES-GCM is it necessary for decryption on the client-side?
GCM is authenticated encryption. The authentication tag is required for decryption. It is used to check the authenticity of the ciphertext and only when this is confirmed decryption is performed.
Since the tag is not applied in your WebCrypto Code, authentication and therefore decryption fail.
WebCrypto expects that the tag is appended to the ciphertext: ciphertext | tag.
The data in the code below was created using your NodeJS code (please note that there is a bug in the NodeJS code: instead of the IV, the key is stored in output):
decrypt();
function decrypt() {
let KEY = hexStringToArrayBuffer('684aa9b1bb4630f802c5c0dd1428403a2224c98126c1892bec0de00b65cc42ba');
let IV = hexStringToArrayBuffer('775a446e052b185c05716dd1955343bb');
let encryptedHex = 'a196a7426a9b1ee64c2258c1575702cf66999a9c42290a77ab2ff30037e5901243170fd19c0092eed4f1f8';
let TAGHex = '14c03526e18502e4c963f6055ec1e9c0';
let encrypted = hexStringToArrayBuffer(encryptedHex + TAGHex)
window.crypto.subtle.importKey(
'raw',
KEY,
'AES-GCM',
true,
['decrypt']
).then((importedKey)=> {
window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: IV,
},
importedKey,
encrypted
).then((plaintext)=>{
console.log('plainText: ', ab2str(plaintext));
});
});
}
function hexStringToArrayBuffer(hexString) {
hexString = hexString.replace(/^0x/, '');
if (hexString.length % 2 != 0) {
console.log('WARNING: expecting an even number of characters in the hexString');
}
var bad = hexString.match(/[G-Z\s]/i);
if (bad) {
console.log('WARNING: found non-hex characters', bad);
}
var pairs = hexString.match(/[\dA-F]{2}/gi);
var integers = pairs.map(function(s) {
return parseInt(s, 16);
});
var array = new Uint8Array(integers);
return array.buffer;
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
I need to decrypt a hex message in JavaScript that has the exact same outcome as the code written in Java. However the Javascript version using CryptoJs returns an empty result
Code in Java:
private static void create()
{
byte[] sessionKey = fromHexString("dae25b4defd646cd99b7b95d450d6646");
byte[] data = fromHexString("2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88");
byte[] payload_data = new byte[48];
byte[] decrypted_data = new byte[48];
for(int i=0;i<48;i++) {
payload_data[i]= data[3+i];
}
try{
SecretKeySpec skeySpec = new SecretKeySpec(sessionKey, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
decrypted_data = cipher.doFinal(payload_data);
}catch(Exception e){
System.out.println(e);
}
String my_data = byteArrayToHex(decrypted_data);
System.out.println(my_data);
}
private static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02X", b));
return sb.toString();
}
private static byte[] fromHexString(String src) {
byte[] biBytes = new BigInteger("10" + src.replaceAll("\\s", ""), 16).toByteArray();
return Arrays.copyOfRange(biBytes, 1, biBytes.length);
}
which returns a result of: "248A8143837F51E03C3522934DD47C38612C90EC57D79D7DE6174EAC85B75F9ADCD7D6686EBF4B9F2E9FE441D373E69E"
Code in JavaScript:
import * as cryptojs from 'crypto-js';
export function create() {
const sessionKey = Buffer.from('dae25b4defd646cd99b7b95d450d6646', 'hex');
const data = Buffer.from('2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88', 'hex');
const payloadData = Buffer.alloc(48);
for (let i = 0; i < 48; i += 1) {
payloadData[i] = data[3 + i];
}
const decrypted = cryptojs.AES.decrypt(
cryptojs.enc.Hex.parse(toHexString(payloadData)),
cryptojs.enc.Hex.parse(toHexString(sessionKey)),
{
mode: cryptojs.mode.ECB,
padding: cryptojs.pad.NoPadding,
}
).toString(cryptojs.enc.Hex);
console.log({
decrypted,
});
}
function toHexString(byteArray) {
// eslint-disable-next-line no-bitwise
return Array.prototype.map.call(byteArray, byte => `0${(byte & 0xff).toString(16)}`.slice(-2)).join('');
}
result:
{ decrypted: '' }
Any idea on what might be wrong ?
The decryption with CryptoJS could look as follows:
function decrypt() {
var sessionKey = 'dae25b4defd646cd99b7b95d450d6646';
var data = '2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88';
var payload_data = data.substr(6);
var decrypted = CryptoJS.AES.decrypt(
payload_data,
CryptoJS.enc.Hex.parse(sessionKey),
{
format: CryptoJS.format.Hex,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding,
}
).toString(CryptoJS.enc.Hex);
console.log(decrypted.replace(/(.{48})/g,'$1\n'));
}
decrypt();
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Update: Regarding your comment: The issue in your code is that the ciphertext is passed as WordArray. The following two changes are one way to make it work:
...
const decrypted = cryptojs.AES.decrypt(
toHexString(payloadData), // pass the ciphertext as hex encoded string
cryptojs.enc.Hex.parse(toHexString(sessionKey)),
{
format: cryptojs.format.Hex, // specify the encoding of the ciphertext
mode: cryptojs.mode.ECB,
...
cryptojs.AES.decrypt() expects the ciphertext in a CipherParams object (and not simply in a WordArray). Alternatively the ciphertext can be passed Base64 encoded or in another encoding that must be specified explicitly with the format parameter (e.g. hexadecimal here, Base64 is the default). The ciphertext is then implicitly converted into a CipherParams object, see here.
But please consider: Since all conversions can be done with CryptoJS onboard means, helpers like toHexString() are not really necessary. For this there are especially the encoder classes, see here. The same applies to the NodeJS Buffer. It makes more sense to work with WordArrays, because they are processed directly by CryptoJS.
Before I get too much into the details, the high level thing I'm trying to accomplish is encrypting some data in JavaScript, sending that to a web server, then decrypting that encrypted data in C#. The part I'm having trouble with is decrypting the data in C#.
I'm encrypting some data in JavaScript like this (I removed the extraneous code):
// https://github.com/diafygi/webcrypto-examples#rsa-oaep---encrypt
window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
data
)
.then(function (encrypted) {
// ...
});
I confirmed that I can decrypt it in JavaScript like so (note that I don't actually want to do this, but I did it to prove that the data could be decrypted):
function decryptValue () {
// Base64 decode the encrypted data for the value "Bob".
var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
// Get private key.
var keyPromise = importPrivateKey();
return keyPromise.then(function (privateKey) {
// Decrypt the value.
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
data
)
.then(function (decrypted) {
// Log the decrypted value to the console.
console.log(arrayBufferToString(decrypted));
});
});
}
For simplicity, that code sample is decrypting a previously encrypted value of "Bob". This works fine.
The problem occurs when I try to decrypt the value in C#:
public static string Decrypt()
{
// The encrypted and base64 encoded value for "Bob".
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
// Assuming RSA-OAEP.
var doOaep = true;
// Setup encryption algorithm.
var provider = GetPrivateKey();
// Decrypt value.
var encryptedData = Convert.FromBase64String(encryptedValue);
// This line throws an error: "Error occurred while decoding OAEP padding."
var decryptedData = provider.Decrypt(encryptedData, doOaep);
var decryptedText = Encoding.Unicode.GetString(decryptedData);
// Return decrypted text.
return decryptedText;
}
The line that says provider.Decrypt(encryptedData, doOaep) throws an error with a message of "Error occurred while decoding OAEP padding." The stack trace is:
Error occurred while decoding OAEP padding.
at System.Security.Cryptography.RSACryptoServiceProvider.DecryptKey(SafeKeyHandle pKeyContext, Byte[] pbEncryptedKey, Int32 cbEncryptedKey, Boolean fOAEP, ObjectHandleOnStack ohRetDecryptedKey)
at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP)
It seems like maybe the way the JavaScript is encrypting the value is not compatible with the way the C# is encrypting the value. Before I completely abandon this approach and try another JavaScript library for encryption, is there some way around this error?
For additional context, I am guessing this error is related to something mentioned in this article: https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry
It says:
Incompatible padding scheme from the JavaScript code would produce the
"bad data" exception at the server side.
The JavaScript code therefore needs to implement one of two padding
schemes used in the .NET RSA implementation, the first is PKCS#1 v1.5
padding and another is OAEP (PKCS#1 v2) padding.
I'm not getting that exact exception, but maybe since that article was written the error message has changed. In any event, what that article says seems to imply that the way the JavaScript is encrypting isn't compatible with the way the C# is decrypting (namely, due to C#'s requirement for padding).
Is there something I'm missing? Is there some parameter or some easy way to get encryption working in JavaScript and decryption working in C#? Perhaps there is some C# library that decrypts in a way that is compatible with the way the JavaScript is encrypting?
Here's a full example that shows the JavaScript is decrypting properly (only works on some browsers... probably not going to work on IE):
function decryptValue () {
// Base64 decode the encrypted data for the value "Bob".
var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
// Get private key.
var keyPromise = importPrivateKey();
return keyPromise.then(function (privateKey) {
// Decrypt the value.
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
data
)
.then(function (decrypted) {
// Log the decrypted value to the console.
console.log("Decrypted value: " + arrayBufferToString(decrypted));
});
});
}
function importPrivateKey() {
var rawKey = {
"alg": "RSA-OAEP-256",
"d": "E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ",
"dp": "kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE",
"dq": "qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE",
"e": "AQAB",
"ext": true,
"key_ops": [
"decrypt"
],
"kty": "RSA",
"n": "oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q",
"p": "2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk",
"q": "vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU",
"qi": "Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo"
};
return window.crypto.subtle.importKey(
"jwk",
rawKey,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
true,
["decrypt"]
);
}
function arrayBufferToString(buffer) {
var result = '';
var bytes = new Uint8Array(buffer);
for (var i = 0; i < bytes.length; i++) {
result += String.fromCharCode(bytes[i]);
}
return result;
}
// Decodes a base64 encoded string into an ArrayBuffer.
// https://stackoverflow.com/a/36378903/2052963
function base64Decode(base64) {
var binary_string = window.atob(base64);
return stringToArrayBuffer(binary_string);
}
// Converts a string to an ArrayBuffer.
function stringToArrayBuffer(value) {
var bytes = new Uint8Array(value.length);
for (var i = 0; i < value.length; i++) {
bytes[i] = value.charCodeAt(i);
}
return bytes.buffer;
}
decryptValue();
BTW, some of my code samples show the private key I'm using. That's intentional to help you understand the code (it's a throw away key). In fact, here's how I am getting the private key in C#:
private static RSACryptoServiceProvider GetPrivateKey()
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = Base64UrlDecode("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
RSAparams.Exponent = Base64UrlDecode("AQAB");
RSAparams.D = Base64UrlDecode("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
RSAparams.P = Base64UrlDecode("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
RSAparams.Q = Base64UrlDecode("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
RSAparams.DP = Base64UrlDecode("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
RSAparams.DQ = Base64UrlDecode("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
RSAparams.InverseQ = Base64UrlDecode("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
RSA.ImportParameters(RSAparams);
return RSA;
}
// From the PDF here: https://www.rfc-editor.org/info/rfc7515
// Also see: https://auth0.com/docs/jwks
public static byte[] Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace('-', '+'); // 62nd char of encoding
s = s.Replace('_', '/'); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception(
"Illegal base64url string!");
}
return Convert.FromBase64String(s); // Standard base64 decoder
}
Because you're using OAEP with SHA-2-256 you need to move from RSACryptoServiceProvider to RSACng (.NET 4.6+). Note that aside from the ctor call, I've eliminated the knowledge of which implementation is being used.
private static RSA GetPrivateKey()
{
// build the RSAParams as before, then
RSA rsa = new RSACng();
rsa.ImportParameters(RSAparams);
return rsa;
}
// Setup encryption algorithm.
var provider = GetPrivateKey();
...
var decryptedData = provider.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
I am unable to test #bartonjs's answer because I don't have access to a Windows computer and Mono apparently doesn't implement RSACng. Below is an example that decrypts your ciphertext using the Bouncycastle C# library. Notice the OaepPadding(...) uses SHA-256 for both the Oaep hash and the MGF hash. This is apparently what is needed to interoperate with your javascript code. Also, notice I used Encoding.UTF8.GetString() whereas you used Encoding.Unicode.GetString(). The encoding is definitely not UTF-16 which is what Encoding.Unicode gives you.
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
namespace RsaSha256OaepDecrypt
{
class MainClass
{
public static void Main(string[] args)
{
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
var encryptedData = Convert.FromBase64String(encryptedValue);
var rsaPrivate = GetPrivateKey();
IAsymmetricBlockCipher cipher0 = new RsaBlindedEngine();
cipher0 = new OaepEncoding(cipher0, new Sha256Digest(), new Sha256Digest(), null);
BufferedAsymmetricBlockCipher cipher = new BufferedAsymmetricBlockCipher(cipher0);
cipher.Init(false, rsaPrivate);
cipher.ProcessBytes(encryptedData, 0, encryptedData.Length);
var decryptedData = cipher.DoFinal();
var decryptedText = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine(decryptedText);
}
private static BigInteger makeBigInt(String b64Url)
{
var bytes = Base64UrlDecode(b64Url);
if ((sbyte)bytes[0] < 0)
{
// prepend a zero byte to make it positive.
var bytes1 = new byte[bytes.Length + 1];
bytes1[0] = 0;
bytes.CopyTo(bytes1, 1);
bytes = bytes1;
}
return new BigInteger(bytes);
}
private static AsymmetricKeyParameter GetPrivateKey()
{
//RSAParameters RSAparams = new RSAParameters();
var Modulus = makeBigInt("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
var Exponent = makeBigInt("AQAB");
var D = makeBigInt("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
var P = makeBigInt("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
var Q = makeBigInt("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
var DP = makeBigInt("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
var DQ = makeBigInt("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
var InverseQ = makeBigInt("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
var rsa = new RsaPrivateCrtKeyParameters(Modulus, Exponent, D, P, Q, DP, DQ, InverseQ);
return rsa;
}
// From the PDF here: https://www.rfc-editor.org/info/rfc7515
// Also see: https://auth0.com/docs/jwks
public static byte[] Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace('-', '+'); // 62nd char of encoding
s = s.Replace('_', '/'); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception(
"Illegal base64url string!");
}
return Convert.FromBase64String(s); // Standard base64 decoder
}
}
}