DOMException during RSA SubtleCrypto decrypt - encryption

I'm attempting to use SubtleCrypto to encrypt a string, store the encrypted string, and decrypt that string again, all using a generated RSA-OAEP key pair.
The below code produces a DOMException during the decryption phase, however I can't seem to get any detail on the error. I've tried using "SHA-1" for hashing but have the same issue.
Any hints?
let encoder = new TextEncoder();
let decoder = new TextDecoder('utf-8');
// Generate a key pair
let keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
let publicKey = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
let privateKey = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
// Encrypt a string
let encodedSecret = encoder.encode("MYSECRETVALUE");
let pubcryptokey = await window.crypto.subtle.importKey(
'jwk',
publicKey,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["encrypt"]
);
let encrypted = await window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
pubcryptokey,
encodedSecret
);
let encDV = new DataView(encrypted);
let ct = decoder.decode(encDV);
// Decrypt the string
let encodedCiphertext = encoder.encode(ct);
let privcryptokey = await window.crypto.subtle.importKey(
'jwk',
privateKey,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["decrypt"]
);
console.log("Before decrypt");
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privcryptokey,
encodedCiphertext
);
console.log("After decrypt");
let decDV = new DataView(decrypted);
let pt = decoder.decode(decDV);
console.log(pt);

The problem is the UTF-8 encoding/decoding of the ciphertext, which corrupts the data. If arbitrary binary data, such as ciphertexts, is to be stored in a string, a binary-to-text encoding such as Base64 must be used, see e.g. here.
If this is fixed, decryption works:
(async () => {
let encoder = new TextEncoder();
let decoder = new TextDecoder('utf-8');
// Generate a key pair
let keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
let publicKey = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
let privateKey = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
// Encrypt a string
let encodedSecret = encoder.encode("MYSECRETVALUE");
let pubcryptokey = await window.crypto.subtle.importKey(
'jwk',
publicKey,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["encrypt"]
);
let encrypted = await window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
pubcryptokey,
encodedSecret
);
//let encDV = new DataView(encrypted); // Bug: UTF-8 decoding damages the ciphertext
//let ct = decoder.decode(encDV);
let ct = ab2b64(encrypted); // Fix: Use a binary to text encoding like Base64
console.log(ct.replace(/(.{48})/g,'$1\n'));
// Decrypt the string
//let encodedCiphertext = encoder.encode(ct); // Bug: s. above
let encodedCiphertext = b642ab(ct); // Fix: s. above
let privcryptokey = await window.crypto.subtle.importKey(
'jwk',
privateKey,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["decrypt"]
);
console.log("Before decrypt");
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privcryptokey,
encodedCiphertext
);
console.log("After decrypt");
let decDV = new DataView(decrypted);
let pt = decoder.decode(decDV);
console.log(pt);
})();
// https://stackoverflow.com/a/11562550/9014097
function ab2b64(arrayBuffer) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
// https://stackoverflow.com/a/41106346
function b642ab(base64string){
return Uint8Array.from(atob(base64string), c => c.charCodeAt(0));
}

Related

How can I upload an image to firebase storage and add it to the database?

I'm new to Vuejs. I want to have a form using which you can add products. The product image goes to firebase storage but how do I associate that image with the exact product in the database?
I've already set up my form, and created two methods. saveProduct() to save the products to the database and onFilePicked() to listen for changes in the input field and target the image and upload that to storage.
import { fb, db } from '../firebaseinit'
export default {
name: 'addProduct',
data () {
return {
product_id: null,
name: null,
desc: null,
category: null,
brand: null,
image: null,
}
},
methods: {
saveProduct () {
db.collection('products').add({
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
})
.then(docRef => {
this.$router.push('/fsbo/produkten')
})
},
onFilePicked (event) {
let imageFile = event.target.files[0]
let storageRef = fb.storage().ref('products/' + imageFile.name)
storageRef.put(imageFile)
}
}
}
what about this, you can use the filename, your images are going to be served as somefireurl.com/{your_file_name} on your product collection you can have an image prop with the imageFile.name.
methods: {
saveProduct (image = null) {
let productRef = db.collection('products').doc(this.product_id)
const payload = {
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
}
if (image) payload['image'] = image
return productRef
.set(payload, {merge: true})
.then(docRef => {
this.$router.push('/fsbo/produkten')
})
},
onFilePicked (event) {
let imageFile = event.target.files[0]
let storageRef = fb.storage().ref('products/' + imageFile.name)
storageRef.put(imageFile)
return this.saveProduct(imageFile.name)
}
}
That should be enough to get you started, maybe you want to try a different combination, or maybe you dont want to call saveProduct the way I set it, it's up to your use case but the idea is the same. Hope this can help you
I fixed it myself. Here's my solution. I don't know if it's technically correct but it works for my use case.
methods: {
saveProduct () {
let imageFile
let imageFileName
let ext
let imageUrl
let key
let task
db.collection('products').add({
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
})
.then(docRef => {
key = docRef.id
this.$router.push('/fsbo/produkten')
return key
})
.then(key => {
if(this.image !== null) {
this.onFilePicked
imageFile = this.image
imageFileName = imageFile.name
ext = imageFileName.slice(imageFileName.lastIndexOf('.'))
}
let storageRef = fb.storage().ref('products/' + key + '.' + ext)
let uploadTask = storageRef.put(imageFile)
uploadTask.on('state_changed', (snapshot) => {}, (error) => {
// Handle unsuccessful uploads
}, () => {
uploadTask.snapshot.ref.getDownloadURL().then( (downloadURL) => {
db.collection('products').doc(key).update({ imageUrl: downloadURL})
});
});
})
},
onFilePicked (event) {
return this.image = event.target.files[0]
}
}

How to dynamically update an attribute in a dynamodb item?

I created an item in dynamodb using Node js, the item has multiple attributes such as brand, category, discount, validity, etc. I am using uuid to generate ids for each item. Now let's say I want to update the validity attribute of the item, in which case I am currently sending the entire json object with the value of validity modified to the new value.
This is definitely not optimal, please help me find an optimal solution.
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: {
'#discount': 'discount',
},
ExpressionAttributeValues: {
':brand': data.brand,
':category': data.category,
':discount': data.discount,
':denominations': data.denominations,
":validity": data.validity,
":redemption": data.redemption
},
UpdateExpression: 'SET #discount = :discount, denominations = :denominations, brand = :brand, category = :category, validity = :validity, redemption = :redemption',
ReturnValues: 'ALL_NEW',
};
I want to send just the attribute I want to update with the new value, if I want to change the validity from 6 months to 8 months, I should just send something like:
{
"validity": "8 months"
}
And it should update the validity attribute of the item.
Same should apply to any other attribute of the item.
'use strict';
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const data = JSON.parse(event.body);
let attr = {};
let nameobj = {};
let exp = 'SET #';
let arr = Object.keys(data);
let attrname = {};
arr.map((key) => {attr[`:${key}`]=data[key]});
arr.map((key) => {
exp += `${key} = :${key}, `
});
arr.map((key) => {nameobj[`#${key}`]=data[key]});
attrname = {
[Object.keys(nameobj)[0]] : nameobj[Object.keys(nameobj)[0]]
}
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: attrname,
ExpressionAttributeValues: attr,
UpdateExpression: exp,
ReturnValues: 'ALL_NEW',
};
// update the todo in the database
dynamoDb.update(params, (error, result) => {
// handle potential errors
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { 'Content-Type': 'text/plain' },
body: 'Couldn\'t update the card',
});
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
};
callback(null, response);
});
};
Contrary to others comments, this is very possible, use the UpdateItem action.
Language agnostic API docs
JavaScript specific API docs
If you want to dynamically create the query, try something like this:
const generateUpdateQuery = (fields) => {
let exp = {
UpdateExpression: 'set',
ExpressionAttributeNames: {},
ExpressionAttributeValues: {}
}
Object.entries(fields).forEach(([key, item]) => {
exp.UpdateExpression += ` #${key} = :${key},`;
exp.ExpressionAttributeNames[`#${key}`] = key;
exp.ExpressionAttributeValues[`:${key}`] = item
})
exp.UpdateExpression = exp.UpdateExpression.slice(0, -1);
return exp
}
let data = {
'field' : { 'subfield': 123 },
'other': '456'
}
let expression = generateUpdateQuery(data)
let params = {
// Key, Table, etc..
...expression
}
console.log(params)
Output:
{
UpdateExpression: 'set #field = :field, #other = :other',
ExpressionAttributeNames: {
'#field': 'field',
'#other': 'other'
},
ExpressionAttributeValues: {
':field': {
'subfield': 123
},
':other': '456'
}
}
Using Javascript SDK V3:
Import from the right package:
import { DynamoDBClient PutItemCommandInput, UpdateItemCommandInput, UpdateItemCommand } from '#aws-sdk/client-dynamodb';
Function to dynamically do partial updates to the item:
(the code below is typescript can be easily converted to Javascript, just remove the types!)
function updateItem(id: string, item: any) {
const dbClient = new DynamoDBClient({region: 'your-region-here });
let exp = 'set ';
let attNames: any = { };
let attVal: any = { };
for(const attribute in item) {
const valKey = `:${attribute}`;
attNames[`#${attribute}`] = attribute;
exp += `#${attribute} = ${valKey}, `;
const val = item[attribute];
attVal[valKey] = { [getDynamoType(val)]: val };
}
exp = exp.substring(0, exp.length - 2);
const params: UpdateItemCommandInput = {
TableName: 'your-table-name-here',
Key: { id: { S: id } },
UpdateExpression: exp,
ExpressionAttributeValues: attVal,
ExpressionAttributeNames: attNames,
ReturnValues: 'ALL_NEW',
};
try {
console.debug('writing to db: ', params);
const command = new UpdateItemCommand(params);
const res = await dbClient.send(command);
console.debug('db res: ', res);
return true;
} catch (err) {
console.error('error writing to dynamoDB: ', err);
return false;
}
}
And to use it (we can do partial updates as well):
updateItem('some-unique-id', { name: 'some-attributes' });
What i did is create a helper class.
Here is a simple function : Add all the attribute and values that goes into, if the value is null or undefined it won't be in the expression.
I recommande to create a helper class with typescript and add more functions and other stuff like generator of expressionAttributeValues , expressionAttributeNames ... , Hope this help.
function updateExpression(attributes, values) {
const expression = attributes.reduce((res, attribute, index) => {
if (values[index]) {
res += ` #${attribute}=:${attribute},`;
}
return res;
}, "SET ");
return expression.slice(0, expression.length - 1)
}
console.log(
updateExpression(["id", "age", "power"], ["e8a8da9a-fab0-55ba-bae3-6392e1ebf624", 28, undefined])
);
You can use code and generate the params object based on the object you provide. It's just a JavaScript object, you walk through the items so that the update expression only contains the fields you have provided.
This is not really a DynamoDB question in that this is more a general JS coding question.
You can use UpdateItem; to familiarize yourself with DynamoDb queries I would suggest you DynamoDb NoSQL workbench:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html
It can generate snippets for you based on your queries.
DynamoDb NoSQL workbench screenshot query

Swift 4 uploading images to Firebase

Good afternoon everyone, I am trying to upload an image to firebase and display back on a cell..I have just about all the content working except the image (i cant get rid of the error).
Here is the error I am getting
Cannot invoke initializer for type 'Posts' with an argument list of type '(postImageStringUrl: String, content: String!, postId: String)'
//[Save Image]
// Create data in the server
let data = UIImageJPEGRepresentation(self.addedImage.image!, 0.5)
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
let postId = "\(Auth.auth().currentUser!.uid)\(NSUUID().uuidString)"
// Create a reference to the file you want to upload
let imagePath = "postImages\(postId)/postPic.jpg"
storageRef.child(imagePath).putData(data!, metadata: metadata) { (metadata, error) in
if error == nil {
let postRef = self.databaseRef.child("posts").childByAutoId()
let post = Posts(postImageStringUrl: String (describing: metadata!.downloadURL()), content: descriptionTextView.text, postId: postId)
postRef.setValue(post.toAnyObject())
}else{
print(error.debugDescription)
}
}
//[Save Image]
Here is my postViewController
import Foundation
import Firebase
import FirebaseDatabase
import FirebaseStorage
struct Posts {
var postImageStringUrl: String!
var department: String!
var content: String!
var username: String!
var postId: String!
var ref: DatabaseReference?
var key: String!
init(postImageStringUrl: String, department: String, content: String, username: String,postId: String, key: String = ""){
self.postImageStringUrl = postImageStringUrl
self.department = department
self.content = content
self.username = username
self.postId = postId
self.key = key
self.ref = Database.database().reference()
}
init(snapshot: DataSnapshot){
let snapshotValue = snapshot.value as! NSMutableDictionary
self.postImageStringUrl = snapshotValue["postImageStringUrl"] as! String
self.department = snapshotValue["department"] as! String
self.content = snapshotValue["content"] as! String
self.username = snapshotValue["username"] as! String
self.postId = snapshotValue["postId"] as! String
self.key = snapshot.key
self.ref = snapshot.ref
}
func toAnyObject() -> [String: AnyObject] {
return ["postImageStringUrl": postImageStringUrl as AnyObject, "department": department as AnyObject,"content": content as AnyObject,"username": username as AnyObject, "postId": postId as AnyObject]
}
}
Any help will be greatly appreciated...Ty
Your Posts init is
init(
postImageStringUrl: String,
department: String,
content: String,
username: String,
postId: String,
key: String = "")
and it appears you are trying to initialize it with
let post = Posts(
postImageStringUrl: String (describing: metadata!.downloadURL()),
content: descriptionTextView.text,
postId: postId)
Basically just missing a couple of parameters in the initialization; department, username

WebCrypto: Importing rsa public key with modulus and exponent using crypto.subtle.importKey

I generated rsa key by jsbn:
{
"e": "10001",
"n": "74a258004dd6c338b27688c1dd13d90da3269321d6dfa3cd8d7cfd7521453d0c5a9828bd507cfc6fe004ddcc09c498d2ceea8e05c214a63ab99d33c9060236f02d5f8bd1e7b334c7f74745948664aeba1a5addf7e7d76911ebf382852aabe86e06f83e0f7669be172380069547f542d8b0848b8bcf53e57c04d5e4163820ced3e4078418efe98df9f8c54c0cda66db3262f20b81464162c44216ca8b63c8f0cfe090dfe1d1950428ad6948204f3f44ba0648de44a9c44d44b91bd8f9ff7cccaceb9f20204f3ba1e228f13249fe04a7fc69cfe57d35e8897e16bc7872f585c909fec9b95a5240ab6589c3ebbe3ad614bfbdc966218daf9d9dddb39fdf6c0d3b49",
...}
"e" is exponent and "n" is the modulus
And I want to import it using crypto.subtle.importKey.
This is the error that I find:
DOMException: The JWK member "n" could not be base64url decoded or contained padding
Does anybody know where is the problem?
Look at the my code below.
var keyData = {
kty: 'RSA',
e: hexToBase64(rsaJson.e),
n: hexToBase64(rsaJson.n),
alg: 'RSA-OAEP-256',
ext: true
};
var algo = {
name: 'RSA-OAEP',
hash: {name: 'SHA-256'}
};
var importedKey = crypto.subtle.importKey('jwk', keyData, algo, false, ['encrypt']).catch(function(err) {
console.log(err);
});
You need to provide a JWK key with values encoded in base64url which is slightly different from base64. Convert the result of hexToBase64 to base64url encoding
This code will work
var keyData = {
kty: 'RSA',
e: b64tob64u(hexToBase64(rsaJson.e)),
n: b64tob64u(hexToBase64(rsaJson.n)),
alg: 'RSA-OAEP-256',
ext: true
};
var algo = {
name: 'RSA-OAEP',
hash: {name: 'SHA-256'}
};
crypto.subtle.importKey('jwk', keyData, algo, false, ['encrypt'])
.then (function (importedKey){
console.log(importedKey);
}).catch(function(err) {
console.log(err);
});
function b64tob64u(a){
a=a.replace(/\=/g,"");
a=a.replace(/\+/g,"-");
a=a.replace(/\//g,"_");
return a
}
Note also WebCryptographyApi works with promises and is async. I changed
var importedKey = crypto.subtle.importKey(params...)
with
crypto.subtle.importKey(params...).then (function (importedKey)

How to use encryption in ydn-db database library?

I am trying to implement setSecret to encrypt the values in indexeddb but get the error:
"unable to call method setSecret of undefined"
Code Below:
$(window).load(function () {
//Database Schema
var db_schema = {
stores: [
{
name: "Employees",
}
]
}
var secret = "Test";
db.setSecret(secret);
db = new ydn.db.Storage('Database', db_schema);
});
Following the link, but not sure where I'm going wrong any ideas:
Anyone encrpyted there indexddb values before?
Thanks
Sorry for documentation behind. I have updated the documentation. Now you have to put encryption key in database options.
var schema = {
version: 1,
stores: [{
name: 'encrypt-store',
encrypted: true
}]
};
var options = {
Encryption: {
expiration: 1000*15, // optional data expiration in ms.
secrets: [{
name: 'aaaa',
key: 'aYHF6vfuGHpfWSeRLrPQxZjS5c6HjCscqDqRtZaspJWSMGaW'
}]
}
};
var db = new ydn.db.Storage('encrypted-db-name', schema, options);
You have to use "-crypt" module to get that feature. It supports transparent data encryption using sha256 crypto, per record salting and key rotation. You still need server to generate encryption key and send to the client securely. There is also a demo app.
//Database Creation
//Database Schema
var db_schema = {
stores: [{
name: "DataTable",
encrypted: true
}
]
};
var options = {
Encryption: {
//expiration: 1000 * 15, // optional data expiration in ms.
secrets: [{
name: 'aaaa',
key: 'aYHF6vfuGHpfWS*eRLrPQxZjSó~É5c6HjCscqDqRtZasp¡JWSMGaW'
}]
}
};
//Create the database with the relevant tables using the schema above
db = new ydn.db.Storage('Database', db_schema,options);
$.ajax({
type: "POST",
url: "/home/DownloadData",
data: { File: file },
success: function (result) {
var Key = result.Data.Key;
var DownloadedData= {
Data: result.Data,
Data1: result.Data1,
Data2: result.Data2,
Data3: result.Data3
};
db.put('DataTable', DownloadedData, Key);
return false;
},
error: function (error) {
alert("fail");
}
});

Resources