Skip to content

Commit ef3bfa7

Browse files
committed
BREAKING CHANGE: switch from crypto-js to webcrypto api (resolve #42)
1 parent 332f681 commit ef3bfa7

File tree

7 files changed

+162
-53
lines changed

7 files changed

+162
-53
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "anchr-multi-webservice",
3-
"version": "2.1.0",
3+
"version": "3.0.0",
44
"description": "⚓️ Anchr provides you with a toolbox for tiny tasks on the internet, especially bookmark collections",
55
"private": true,
66
"scripts": {

public/app/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@
140140
<script src="bower_components/snackbarjs/dist/snackbar.min.js"></script>
141141
<script src="bower_components/ng-file-upload/ng-file-upload.js"></script>
142142
<script src="bower_components/angular-jwt/dist/angular-jwt.js"></script>
143-
<script src="bower_components/crypto-js/crypto-js.js"></script>
144143
<!-- endbower -->
145144
<!-- endbuild -->
146145

@@ -152,6 +151,7 @@
152151
<script src="scripts/services/remote.js"></script>
153152
<script src="scripts/services/image.js"></script>
154153
<script src="scripts/services/collection.js"></script>
154+
<script src="scripts/services/encryption.js"></script>
155155
<script src="scripts/filters/imageLinkFilters.js"></script>
156156
<script src="scripts/directives/myenter.js"></script>
157157
<script src="scripts/directives/apicall.js"></script>

public/app/scripts/controllers/image.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

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

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

19-
reader.onload = function(e) {
20-
var encrypted = CryptoJS.AES.encrypt(e.target.result, password);
21-
var blob = new Blob([encrypted], {type: file.type});
22-
blob.name = file.name;
23-
blob.encrypted = true;
24-
$scope.files.loading = false;
25-
$scope.uploadFiles([blob], []);
19+
reader.onload = function (e) {
20+
Encryption.encrypt(e.target.result, password)
21+
.then(function (encrypted) {
22+
var blob = new Blob([encrypted], { type: file.type });
23+
blob.name = file.name;
24+
blob.encrypted = true;
25+
$scope.files.loading = false;
26+
$scope.uploadFiles([blob], []);
27+
})
28+
.catch(function(error) {
29+
console.error(error);
30+
31+
$scope.files.loading = false;
32+
Snackbar.show("Failed to encrypt image.");
33+
});
2634
};
2735

28-
reader.readAsDataURL(file);
36+
reader.readAsArrayBuffer(file);
2937
};
3038

31-
$scope.uploadFiles = function(files, errFiles) {
39+
$scope.uploadFiles = function (files, errFiles) {
3240
if ((files && files.length) || (errFiles && errFiles.length)) {
3341
$scope.files.files = $scope.files.files.concat(files);
3442
$scope.files.errFiles = $scope.files.errFiles.concat(errFiles);
@@ -41,7 +49,7 @@ angular.module('anchrClientApp')
4149
else {
4250
file.upload = Upload.upload({
4351
url: $rootScope.getApiUrl() + 'image',
44-
data: {uploadFile: file, encrypted: file.encrypted}
52+
data: { uploadFile: file, encrypted: file.encrypted }
4553
});
4654

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

6472
$scope.clear = function () {
6573
$scope.files = {
66-
files : [],
74+
files: [],
6775
errFiles: [],
6876
password: null,
6977
encrypt: false,
@@ -79,8 +87,8 @@ angular.module('anchrClientApp')
7987
$rootScope.init();
8088
}
8189

82-
function arrayMatch (regexArray, val) {
83-
for (var i=0; i<regexArray.length; i++) {
90+
function arrayMatch(regexArray, val) {
91+
for (var i = 0; i < regexArray.length; i++) {
8492
if (val.match(regexArray[i])) return true;
8593
}
8694
return false;
Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,55 @@
11
'use strict';
22

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

77
$scope.image = {};
88
$scope.data = {
9-
password : '',
10-
imgSrc : null,
9+
password: '',
10+
imgSrc: null,
1111
loading: false
1212
};
1313

14-
Image.get.get({id: id}, function(result) {
14+
Image.get.get({ id: id }, function (result) {
1515
$scope.image = result;
1616
$scope.image.created = Date.parse($scope.image.created);
1717
$scope.image.link = $location.absUrl();
1818
});
1919

2020
$scope.decrypt = function () {
21-
var relativeHref = new URL($scope.image.href).pathname
2221
$scope.data.loading = true;
23-
$http.get(relativeHref).then(function (result) {
24-
var password = $scope.data.password;
25-
var reader = new FileReader();
26-
var blob = new Blob([result.data], { type: $scope.image.type });
27-
28-
reader.onload = function(e){
29-
30-
var decrypted = CryptoJS.AES.decrypt(e.target.result, password).toString(CryptoJS.enc.Latin1);
31-
32-
if(!/^data:/.test(decrypted)){
33-
$scope.data.loading = false;
34-
$scope.data.password = '';
35-
$scope.$apply();
36-
Snackbar.show("Invalid password.");
37-
return false;
38-
}
3922

40-
$scope.data.imageSrc = decrypted;
41-
$scope.data.loading = false;
42-
$scope.$apply();
43-
};
44-
45-
reader.readAsText(blob);
46-
47-
}, function (err) {
48-
console.log(err)
49-
});
23+
var relativeHref = new URL($scope.image.href).pathname
24+
$http.get(relativeHref, { responseType: 'arraybuffer' })
25+
.then(function (result) {
26+
var password = $scope.data.password;
27+
28+
Encryption.decrypt(result.data, password)
29+
.then(function (decrypted) {
30+
var reader = new FileReader();
31+
var blob = new Blob([decrypted], { type: $scope.image.type });
32+
33+
reader.onload = function (event) {
34+
var result = event.target.result;
35+
36+
$scope.data.imageSrc = result;
37+
$scope.data.loading = false;
38+
$scope.$apply();
39+
}
40+
41+
reader.readAsDataURL(blob)
42+
})
43+
.catch(function (error) {
44+
console.error(error);
45+
46+
$scope.data.loading = false;
47+
$scope.data.password = '';
48+
$scope.$apply();
49+
Snackbar.show("Invalid password.");
50+
})
51+
}, function (err) {
52+
console.log(err)
53+
});
5054
};
5155
}]);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
function appendBuffer(buffer1, buffer2) {
2+
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
3+
tmp.set(new Uint8Array(buffer1), 0);
4+
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
5+
return tmp.buffer;
6+
};
7+
8+
function getKeyMaterial(password) {
9+
let enc = new TextEncoder();
10+
return window.crypto.subtle.importKey(
11+
'raw',
12+
enc.encode(password),
13+
{ name: 'PBKDF2' },
14+
false,
15+
['deriveBits', 'deriveKey']
16+
);
17+
}
18+
19+
function getKey(keyMaterial, salt) {
20+
return window.crypto.subtle.deriveKey(
21+
{
22+
'name': 'PBKDF2',
23+
salt: salt,
24+
'iterations': 100000,
25+
'hash': 'SHA-256'
26+
},
27+
keyMaterial,
28+
{ 'name': 'AES-GCM', 'length': 256 },
29+
true,
30+
['encrypt', 'decrypt']
31+
);
32+
}
33+
34+
function encrypt(data, key, iv) {
35+
return window.crypto.subtle.encrypt(
36+
{
37+
name: 'AES-GCM',
38+
iv: iv
39+
},
40+
key,
41+
data
42+
)
43+
}
44+
45+
function decrypt(data, key, iv) {
46+
return window.crypto.subtle.decrypt(
47+
{
48+
name: 'AES-GCM',
49+
iv: iv
50+
},
51+
key,
52+
data
53+
)
54+
}
55+
56+
angular.module('anchrClientApp')
57+
.factory('Encryption', function () {
58+
return {
59+
/**
60+
* Encrypts an image with a password
61+
* @param {ArrayBuffer} data The image to be encrypted
62+
* @param {string} password The password to use for encryption
63+
* @returns {ArrayBuffer} The encrypted binary image data, prefixed with 16 bytes of salt, followed by 12 bytes of initialization vector
64+
*/
65+
encrypt: function (data, password) {
66+
var salt = window.crypto.getRandomValues(new Uint8Array(16));
67+
var iv = window.crypto.getRandomValues(new Uint8Array(12));
68+
69+
return getKeyMaterial(password)
70+
.then(function (keyMaterial) {
71+
return getKey(keyMaterial, salt);
72+
})
73+
.then(function (key) {
74+
return encrypt(data, key, iv);
75+
})
76+
.then(function (result) {
77+
var prefix = appendBuffer(salt, iv);
78+
return appendBuffer(prefix, result);
79+
});
80+
},
81+
/**
82+
* Decrypts an image using a password
83+
* @param {ArrayBuffer} data The encrypted binary image data, prefixed with 16 bytes of salt, followed by 12 bytes of initialization vector
84+
* @param {string} password The password previously used for encryption
85+
* @returns {ArrayBuffer} The decrypted image content
86+
*/
87+
88+
decrypt: function (data, password) {
89+
var salt = data.slice(0, 16);
90+
var iv = data.slice(16, 12 + 16);
91+
var image = data.slice(12 + 16);
92+
93+
return getKeyMaterial(password)
94+
.then(function (keyMaterial) {
95+
return getKey(keyMaterial, salt);
96+
})
97+
.then(function (key) {
98+
return decrypt(image, key, iv);
99+
});
100+
}
101+
}
102+
});

public/bower.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
"ngclipboard": "1.1.3",
1212
"snackbarjs": "1.1.0",
1313
"ng-file-upload": "12.2.13",
14-
"angular-jwt": "0.1.11",
15-
"crypto-js": "4.1.1"
14+
"angular-jwt": "0.1.11"
1615
},
1716
"devDependencies": {
1817
"angular-mocks": "1.4.0"
@@ -30,9 +29,6 @@
3029
"bootstrap-sass": {
3130
"main": [
3231
]
33-
},
34-
"crypto-js": {
35-
"main": "crypto-js.js"
3632
}
3733
},
3834
"resolutions": {

public/test/karma.conf.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ module.exports = function(config) {
3535
'bower_components/snackbarjs/dist/snackbar.min.js',
3636
'bower_components/ng-file-upload/ng-file-upload.js',
3737
'bower_components/angular-jwt/dist/angular-jwt.js',
38-
'bower_components/crypto-js/crypto-js.js',
3938
'bower_components/angular-mocks/angular-mocks.js',
4039
// endbower
4140
"app/scripts/**/*.js",

0 commit comments

Comments
 (0)