Skip to content

Commit

Permalink
BREAKING CHANGE: switch from crypto-js to webcrypto api (resolve #42)
Browse files Browse the repository at this point in the history
  • Loading branch information
muety committed Jan 9, 2022
1 parent 332f681 commit ef3bfa7
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 53 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "anchr-multi-webservice",
"version": "2.1.0",
"version": "3.0.0",
"description": "⚓️ Anchr provides you with a toolbox for tiny tasks on the internet, especially bookmark collections",
"private": true,
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion public/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@
<script src="bower_components/snackbarjs/dist/snackbar.min.js"></script>
<script src="bower_components/ng-file-upload/ng-file-upload.js"></script>
<script src="bower_components/angular-jwt/dist/angular-jwt.js"></script>
<script src="bower_components/crypto-js/crypto-js.js"></script>
<!-- endbower -->
<!-- endbuild -->

Expand All @@ -152,6 +151,7 @@
<script src="scripts/services/remote.js"></script>
<script src="scripts/services/image.js"></script>
<script src="scripts/services/collection.js"></script>
<script src="scripts/services/encryption.js"></script>
<script src="scripts/filters/imageLinkFilters.js"></script>
<script src="scripts/directives/myenter.js"></script>
<script src="scripts/directives/apicall.js"></script>
Expand Down
36 changes: 22 additions & 14 deletions public/app/scripts/controllers/image.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

angular.module('anchrClientApp')
.controller('ImageCtrl', ['$rootScope', '$scope', 'Upload', '$timeout', function ($rootScope, $scope, Upload, $timeout) {
.controller('ImageCtrl', ['$rootScope', '$scope', 'Snackbar', 'Upload', 'Encryption', '$timeout', function ($rootScope, $scope, Snackbar, Upload, Encryption, $timeout) {
var allowedTypes = ['image/'];

$scope.encryptAndUpload = function (files, errFiles) {
Expand All @@ -16,19 +16,27 @@ angular.module('anchrClientApp')
return false;
}

reader.onload = function(e) {
var encrypted = CryptoJS.AES.encrypt(e.target.result, password);
var blob = new Blob([encrypted], {type: file.type});
blob.name = file.name;
blob.encrypted = true;
$scope.files.loading = false;
$scope.uploadFiles([blob], []);
reader.onload = function (e) {
Encryption.encrypt(e.target.result, password)
.then(function (encrypted) {
var blob = new Blob([encrypted], { type: file.type });
blob.name = file.name;
blob.encrypted = true;
$scope.files.loading = false;
$scope.uploadFiles([blob], []);
})
.catch(function(error) {
console.error(error);

$scope.files.loading = false;
Snackbar.show("Failed to encrypt image.");
});
};

reader.readAsDataURL(file);
reader.readAsArrayBuffer(file);
};

$scope.uploadFiles = function(files, errFiles) {
$scope.uploadFiles = function (files, errFiles) {
if ((files && files.length) || (errFiles && errFiles.length)) {
$scope.files.files = $scope.files.files.concat(files);
$scope.files.errFiles = $scope.files.errFiles.concat(errFiles);
Expand All @@ -41,7 +49,7 @@ angular.module('anchrClientApp')
else {
file.upload = Upload.upload({
url: $rootScope.getApiUrl() + 'image',
data: {uploadFile: file, encrypted: file.encrypted}
data: { uploadFile: file, encrypted: file.encrypted }
});

file.upload.then(function (response) {
Expand All @@ -63,7 +71,7 @@ angular.module('anchrClientApp')

$scope.clear = function () {
$scope.files = {
files : [],
files: [],
errFiles: [],
password: null,
encrypt: false,
Expand All @@ -79,8 +87,8 @@ angular.module('anchrClientApp')
$rootScope.init();
}

function arrayMatch (regexArray, val) {
for (var i=0; i<regexArray.length; i++) {
function arrayMatch(regexArray, val) {
for (var i = 0; i < regexArray.length; i++) {
if (val.match(regexArray[i])) return true;
}
return false;
Expand Down
66 changes: 35 additions & 31 deletions public/app/scripts/controllers/view-image.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,55 @@
'use strict';

angular.module('anchrClientApp')
.controller('ViewImageCtrl', ['$scope', '$rootScope', '$http', '$location', 'Snackbar', 'Image', 'id', function ($scope, $rootScope, $http, $location, Snackbar, Image, id) {
.controller('ViewImageCtrl', ['$scope', '$rootScope', '$http', '$location', 'Snackbar', 'Image', 'Encryption', 'id', function ($scope, $rootScope, $http, $location, Snackbar, Image, Encryption, id) {
$rootScope.init();

$scope.image = {};
$scope.data = {
password : '',
imgSrc : null,
password: '',
imgSrc: null,
loading: false
};

Image.get.get({id: id}, function(result) {
Image.get.get({ id: id }, function (result) {
$scope.image = result;
$scope.image.created = Date.parse($scope.image.created);
$scope.image.link = $location.absUrl();
});

$scope.decrypt = function () {
var relativeHref = new URL($scope.image.href).pathname
$scope.data.loading = true;
$http.get(relativeHref).then(function (result) {
var password = $scope.data.password;
var reader = new FileReader();
var blob = new Blob([result.data], { type: $scope.image.type });

reader.onload = function(e){

var decrypted = CryptoJS.AES.decrypt(e.target.result, password).toString(CryptoJS.enc.Latin1);

if(!/^data:/.test(decrypted)){
$scope.data.loading = false;
$scope.data.password = '';
$scope.$apply();
Snackbar.show("Invalid password.");
return false;
}

$scope.data.imageSrc = decrypted;
$scope.data.loading = false;
$scope.$apply();
};

reader.readAsText(blob);

}, function (err) {
console.log(err)
});
var relativeHref = new URL($scope.image.href).pathname
$http.get(relativeHref, { responseType: 'arraybuffer' })
.then(function (result) {
var password = $scope.data.password;

Encryption.decrypt(result.data, password)
.then(function (decrypted) {
var reader = new FileReader();
var blob = new Blob([decrypted], { type: $scope.image.type });

reader.onload = function (event) {
var result = event.target.result;

$scope.data.imageSrc = result;
$scope.data.loading = false;
$scope.$apply();
}

reader.readAsDataURL(blob)
})
.catch(function (error) {
console.error(error);

$scope.data.loading = false;
$scope.data.password = '';
$scope.$apply();
Snackbar.show("Invalid password.");
})
}, function (err) {
console.log(err)
});
};
}]);
102 changes: 102 additions & 0 deletions public/app/scripts/services/encryption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
function appendBuffer(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
};

function getKeyMaterial(password) {
let enc = new TextEncoder();
return window.crypto.subtle.importKey(
'raw',
enc.encode(password),
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
}

function getKey(keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
'name': 'PBKDF2',
salt: salt,
'iterations': 100000,
'hash': 'SHA-256'
},
keyMaterial,
{ 'name': 'AES-GCM', 'length': 256 },
true,
['encrypt', 'decrypt']
);
}

function encrypt(data, key, iv) {
return window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
data
)
}

function decrypt(data, key, iv) {
return window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
data
)
}

angular.module('anchrClientApp')
.factory('Encryption', function () {
return {
/**
* Encrypts an image with a password
* @param {ArrayBuffer} data The image to be encrypted
* @param {string} password The password to use for encryption
* @returns {ArrayBuffer} The encrypted binary image data, prefixed with 16 bytes of salt, followed by 12 bytes of initialization vector
*/
encrypt: function (data, password) {
var salt = window.crypto.getRandomValues(new Uint8Array(16));
var iv = window.crypto.getRandomValues(new Uint8Array(12));

return getKeyMaterial(password)
.then(function (keyMaterial) {
return getKey(keyMaterial, salt);
})
.then(function (key) {
return encrypt(data, key, iv);
})
.then(function (result) {
var prefix = appendBuffer(salt, iv);
return appendBuffer(prefix, result);
});
},
/**
* Decrypts an image using a password
* @param {ArrayBuffer} data The encrypted binary image data, prefixed with 16 bytes of salt, followed by 12 bytes of initialization vector
* @param {string} password The password previously used for encryption
* @returns {ArrayBuffer} The decrypted image content
*/

decrypt: function (data, password) {
var salt = data.slice(0, 16);
var iv = data.slice(16, 12 + 16);
var image = data.slice(12 + 16);

return getKeyMaterial(password)
.then(function (keyMaterial) {
return getKey(keyMaterial, salt);
})
.then(function (key) {
return decrypt(image, key, iv);
});
}
}
});
6 changes: 1 addition & 5 deletions public/bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
"ngclipboard": "1.1.3",
"snackbarjs": "1.1.0",
"ng-file-upload": "12.2.13",
"angular-jwt": "0.1.11",
"crypto-js": "4.1.1"
"angular-jwt": "0.1.11"
},
"devDependencies": {
"angular-mocks": "1.4.0"
Expand All @@ -30,9 +29,6 @@
"bootstrap-sass": {
"main": [
]
},
"crypto-js": {
"main": "crypto-js.js"
}
},
"resolutions": {
Expand Down
1 change: 0 additions & 1 deletion public/test/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ module.exports = function(config) {
'bower_components/snackbarjs/dist/snackbar.min.js',
'bower_components/ng-file-upload/ng-file-upload.js',
'bower_components/angular-jwt/dist/angular-jwt.js',
'bower_components/crypto-js/crypto-js.js',
'bower_components/angular-mocks/angular-mocks.js',
// endbower
"app/scripts/**/*.js",
Expand Down

0 comments on commit ef3bfa7

Please sign in to comment.