mirror of
https://github.com/bitwarden/desktop.git
synced 2024-09-16 02:17:24 +02:00
password generation service to ts
This commit is contained in:
parent
c586349014
commit
8eeb5821f0
@ -15,7 +15,6 @@
|
||||
<script type="text/javascript" src="services/syncService.js"></script>
|
||||
<script type="text/javascript" src="services/autofillService.js"></script>
|
||||
<script type="text/javascript" src="services/appIdService.js"></script>
|
||||
<script type="text/javascript" src="services/passwordGenerationService.js"></script>
|
||||
<script type="text/javascript" src="services/totpService.js"></script>
|
||||
<script type="text/javascript" src="services/environmentService.js"></script>
|
||||
</head>
|
||||
|
@ -4,6 +4,7 @@ import i18nService from './services/i18nService.js';
|
||||
import LockService from './services/lockService.js';
|
||||
import UtilsService from './services/utils.service';
|
||||
import CryptoService from './services/crypto.service';
|
||||
import PasswordGenerationService from './services/passwordGeneration.service';
|
||||
|
||||
// Model imports
|
||||
import { AttachmentData } from './models/data/attachmentData';
|
||||
@ -86,7 +87,7 @@ var bg_isBackground = true,
|
||||
setIcon, refreshBadgeAndMenu);
|
||||
window.bg_syncService = bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService,
|
||||
bg_cryptoService, logout);
|
||||
window.bg_passwordGenerationService = bg_passwordGenerationService = new PasswordGenerationService(bg_constantsService, bg_utilsService, bg_cryptoService);
|
||||
window.bg_passwordGenerationService = bg_passwordGenerationService = new PasswordGenerationService(bg_cryptoService);
|
||||
window.bg_totpService = bg_totpService = new TotpService(bg_constantsService);
|
||||
window.bg_autofillService = bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService,
|
||||
bg_constantsService);
|
||||
@ -101,7 +102,7 @@ var bg_isBackground = true,
|
||||
eventAction: 'Generated Password From Command'
|
||||
});
|
||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||
var password = bg_passwordGenerationService.generatePassword(options);
|
||||
var password = PasswordGenerationService.generatePassword(options);
|
||||
bg_utilsService.copyToClipboard(password);
|
||||
bg_passwordGenerationService.addHistory(password);
|
||||
});
|
||||
@ -236,7 +237,7 @@ var bg_isBackground = true,
|
||||
eventAction: 'Generated Password From Context Menu'
|
||||
});
|
||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||
var password = bg_passwordGenerationService.generatePassword(options);
|
||||
var password = PasswordGenerationService.generatePassword(options);
|
||||
bg_utilsService.copyToClipboard(password);
|
||||
bg_passwordGenerationService.addHistory(password);
|
||||
});
|
||||
|
9
src/models/domain/passwordHistory.ts
Normal file
9
src/models/domain/passwordHistory.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default class PasswordHistory {
|
||||
password: string;
|
||||
date: number;
|
||||
|
||||
constructor(password: string, date: number) {
|
||||
this.password = password;
|
||||
this.date = date;
|
||||
}
|
||||
}
|
@ -17,11 +17,11 @@ const Keys = {
|
||||
|
||||
const SigningAlgorithm = {
|
||||
name: 'HMAC',
|
||||
hash: { name: 'SHA-256' }
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
|
||||
const AesAlgorithm = {
|
||||
name: 'AES-CBC'
|
||||
name: 'AES-CBC',
|
||||
};
|
||||
|
||||
const Crypto = window.crypto;
|
||||
@ -269,7 +269,7 @@ export default class CryptoService {
|
||||
return this.encrypt(bytes, key, 'raw');
|
||||
}
|
||||
|
||||
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey,
|
||||
async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey,
|
||||
plainValueEncoding: string = 'utf8'): Promise<CipherString> {
|
||||
if (!plainValue) {
|
||||
return Promise.resolve(null);
|
||||
@ -307,7 +307,7 @@ export default class CryptoService {
|
||||
return encBytes.buffer;
|
||||
}
|
||||
|
||||
async decrypt(cipherString: CipherString, key: SymmetricCryptoKey,
|
||||
async decrypt(cipherString: CipherString, key?: SymmetricCryptoKey,
|
||||
outputEncoding: string = 'utf8'): Promise<string> {
|
||||
const ivBytes: string = forge.util.decode64(cipherString.initializationVector);
|
||||
const ctBytes: string = forge.util.decode64(cipherString.cipherText);
|
||||
@ -531,7 +531,7 @@ export default class CryptoService {
|
||||
|
||||
private async computeMacWC(dataBuf: ArrayBuffer, macKeyBuf: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const key = await Subtle.importKey('raw', macKeyBuf, SigningAlgorithm, false, ['sign']);
|
||||
return await Subtle.sign(SigningAlgorithm, key, dataBuf);
|
||||
return await Subtle.sign(SigningAlgorithm, key, dataBuf);
|
||||
}
|
||||
|
||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||
|
245
src/services/passwordGeneration.service.ts
Normal file
245
src/services/passwordGeneration.service.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import { CipherString } from '../models/domain/cipherString';
|
||||
import PasswordHistory from '../models/domain/passwordHistory';
|
||||
|
||||
import ConstantsService from './constants.service';
|
||||
import CryptoService from './crypto.service';
|
||||
import UtilsService from './utils.service';
|
||||
|
||||
const DefaultOptions = {
|
||||
length: 10,
|
||||
ambiguous: false,
|
||||
number: true,
|
||||
minNumber: 1,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: true,
|
||||
minLowercase: 1,
|
||||
special: false,
|
||||
minSpecial: 1,
|
||||
};
|
||||
|
||||
const Keys = {
|
||||
options: 'passwordGenerationOptions',
|
||||
};
|
||||
|
||||
const MaxPasswordsInHistory = 100;
|
||||
|
||||
export default class PasswordGenerationService {
|
||||
static generatePassword(options: any): string {
|
||||
// overload defaults with given options
|
||||
const o = UtilsService.extendObject({}, DefaultOptions, options);
|
||||
|
||||
// sanitize
|
||||
if (o.uppercase && o.minUppercase < 0) {
|
||||
o.minUppercase = 1;
|
||||
}
|
||||
if (o.lowercase && o.minLowercase < 0) {
|
||||
o.minLowercase = 1;
|
||||
}
|
||||
if (o.number && o.minNumber < 0) {
|
||||
o.minNumber = 1;
|
||||
}
|
||||
if (o.special && o.minSpecial < 0) {
|
||||
o.minSpecial = 1;
|
||||
}
|
||||
|
||||
if (!o.length || o.length < 1) {
|
||||
o.length = 10;
|
||||
}
|
||||
|
||||
const minLength: number = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
||||
if (o.length < minLength) {
|
||||
o.length = minLength;
|
||||
}
|
||||
|
||||
const positions: string[] = [];
|
||||
if (o.lowercase && o.minLowercase > 0) {
|
||||
for (let i = 0; i < o.minLowercase; i++) {
|
||||
positions.push('l');
|
||||
}
|
||||
}
|
||||
if (o.uppercase && o.minUppercase > 0) {
|
||||
for (let i = 0; i < o.minUppercase; i++) {
|
||||
positions.push('u');
|
||||
}
|
||||
}
|
||||
if (o.number && o.minNumber > 0) {
|
||||
for (let i = 0; i < o.minNumber; i++) {
|
||||
positions.push('n');
|
||||
}
|
||||
}
|
||||
if (o.special && o.minSpecial > 0) {
|
||||
for (let i = 0; i < o.minSpecial; i++) {
|
||||
positions.push('s');
|
||||
}
|
||||
}
|
||||
while (positions.length < o.length) {
|
||||
positions.push('a');
|
||||
}
|
||||
|
||||
// shuffle
|
||||
positions.sort(() => {
|
||||
return UtilsService.secureRandomNumber(0, 1) * 2 - 1;
|
||||
});
|
||||
|
||||
// build out the char sets
|
||||
let allCharSet = '';
|
||||
|
||||
let lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
||||
if (o.ambiguous) {
|
||||
lowercaseCharSet += 'l';
|
||||
}
|
||||
if (o.lowercase) {
|
||||
allCharSet += lowercaseCharSet;
|
||||
}
|
||||
|
||||
let uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
||||
if (o.ambiguous) {
|
||||
uppercaseCharSet += 'O';
|
||||
}
|
||||
if (o.uppercase) {
|
||||
allCharSet += uppercaseCharSet;
|
||||
}
|
||||
|
||||
let numberCharSet = '23456789';
|
||||
if (o.ambiguous) {
|
||||
numberCharSet += '01';
|
||||
}
|
||||
if (o.number) {
|
||||
allCharSet += numberCharSet;
|
||||
}
|
||||
|
||||
const specialCharSet = '!@#$%^&*';
|
||||
if (o.special) {
|
||||
allCharSet += specialCharSet;
|
||||
}
|
||||
|
||||
let password = '';
|
||||
for (let i = 0; i < o.length; i++) {
|
||||
let positionChars: string;
|
||||
switch (positions[i]) {
|
||||
case 'l':
|
||||
positionChars = lowercaseCharSet;
|
||||
break;
|
||||
case 'u':
|
||||
positionChars = uppercaseCharSet;
|
||||
break;
|
||||
case 'n':
|
||||
positionChars = numberCharSet;
|
||||
break;
|
||||
case 's':
|
||||
positionChars = specialCharSet;
|
||||
break;
|
||||
case 'a':
|
||||
positionChars = allCharSet;
|
||||
break;
|
||||
}
|
||||
|
||||
const randomCharIndex = UtilsService.secureRandomNumber(0, positionChars.length - 1);
|
||||
password += positionChars.charAt(randomCharIndex);
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
optionsCache: any;
|
||||
history: PasswordHistory[];
|
||||
|
||||
constructor(private cryptoService: CryptoService) {
|
||||
const self = this;
|
||||
|
||||
const historyKey = ConstantsService.generatedPasswordHistoryKey;
|
||||
UtilsService.getObjFromStorage<PasswordHistory[]>(historyKey).then((encrypted) => {
|
||||
return self.decryptHistory(encrypted);
|
||||
}).then((history) => {
|
||||
self.history = history;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove in favor of static
|
||||
generatePassword(options: any) {
|
||||
return PasswordGenerationService.generatePassword(options);
|
||||
}
|
||||
|
||||
async getOptions() {
|
||||
if (this.optionsCache) {
|
||||
return this.optionsCache;
|
||||
}
|
||||
|
||||
const options = await UtilsService.getObjFromStorage(Keys.options);
|
||||
this.optionsCache = options;
|
||||
return options;
|
||||
}
|
||||
|
||||
async saveOptions(options: any) {
|
||||
await UtilsService.saveObjToStorage(Keys.options, options);
|
||||
this.optionsCache = options;
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
return this.history;
|
||||
}
|
||||
|
||||
async addHistory(password: string) {
|
||||
// Prevent duplicates
|
||||
if (this.matchesPrevious(password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.history.push(new PasswordHistory(password, Date.now()));
|
||||
|
||||
// Remove old items.
|
||||
if (this.history.length > MaxPasswordsInHistory) {
|
||||
this.history.shift();
|
||||
}
|
||||
|
||||
await this.saveHistory();
|
||||
}
|
||||
|
||||
clear(): Promise<any> {
|
||||
this.history = [];
|
||||
return UtilsService.removeFromStorage(ConstantsService.generatedPasswordHistoryKey);
|
||||
}
|
||||
|
||||
private async saveHistory() {
|
||||
const history = await this.encryptHistory();
|
||||
return UtilsService.saveObjToStorage(ConstantsService.generatedPasswordHistoryKey, history);
|
||||
}
|
||||
|
||||
private encryptHistory(): Promise<PasswordHistory[]> {
|
||||
if (this.history == null) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const promises = self.history.map(async (item) => {
|
||||
const encrypted = await self.cryptoService.encrypt(item.password);
|
||||
return new PasswordHistory(encrypted.encryptedString, item.date);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private decryptHistory(history: PasswordHistory[]): Promise<PasswordHistory[]> {
|
||||
if (history == null) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const promises = history.map(async (item) => {
|
||||
const decrypted = await self.cryptoService.decrypt(new CipherString(item.password));
|
||||
return new PasswordHistory(decrypted, item.date);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private matchesPrevious(password: string): boolean {
|
||||
if (this.history == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const len = this.history.length;
|
||||
return len !== 0 && this.history[len - 1].password === password;
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
function PasswordGenerationService(constantsService, utilsService, cryptoService) {
|
||||
this.optionsCache = null;
|
||||
this.constantsService = constantsService;
|
||||
this.utilsService = utilsService;
|
||||
this.cryptoService = cryptoService;
|
||||
this.history = [];
|
||||
|
||||
initPasswordGenerationService(this);
|
||||
}
|
||||
|
||||
function initPasswordGenerationService(self) {
|
||||
var optionsKey = 'passwordGenerationOptions';
|
||||
var defaultOptions = {
|
||||
length: 10,
|
||||
ambiguous: false,
|
||||
number: true,
|
||||
minNumber: 1,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: true,
|
||||
minLowercase: 1,
|
||||
special: false,
|
||||
minSpecial: 1
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.generatePassword = function (options) {
|
||||
// overload defaults with given options
|
||||
var o = extend({}, defaultOptions, options);
|
||||
|
||||
// sanitize
|
||||
if (o.uppercase && o.minUppercase < 0) o.minUppercase = 1;
|
||||
if (o.lowercase && o.minLowercase < 0) o.minLowercase = 1;
|
||||
if (o.number && o.minNumber < 0) o.minNumber = 1;
|
||||
if (o.special && o.minSpecial < 0) o.minSpecial = 1;
|
||||
|
||||
if (!o.length || o.length < 1) o.length = 10;
|
||||
var minLength = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
||||
if (o.length < minLength) o.length = minLength;
|
||||
|
||||
var positions = [];
|
||||
if (o.lowercase && o.minLowercase > 0) {
|
||||
for (var i = 0; i < o.minLowercase; i++) {
|
||||
positions.push('l');
|
||||
}
|
||||
}
|
||||
if (o.uppercase && o.minUppercase > 0) {
|
||||
for (var j = 0; j < o.minUppercase; j++) {
|
||||
positions.push('u');
|
||||
}
|
||||
}
|
||||
if (o.number && o.minNumber > 0) {
|
||||
for (var k = 0; k < o.minNumber; k++) {
|
||||
positions.push('n');
|
||||
}
|
||||
}
|
||||
if (o.special && o.minSpecial > 0) {
|
||||
for (var l = 0; l < o.minSpecial; l++) {
|
||||
positions.push('s');
|
||||
}
|
||||
}
|
||||
while (positions.length < o.length) {
|
||||
positions.push('a');
|
||||
}
|
||||
|
||||
// shuffle
|
||||
positions.sort(function () {
|
||||
return randomInt(0, 1) * 2 - 1;
|
||||
});
|
||||
|
||||
// build out the char sets
|
||||
var allCharSet = '';
|
||||
|
||||
var lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
||||
if (o.ambiguous) lowercaseCharSet += 'l';
|
||||
if (o.lowercase) allCharSet += lowercaseCharSet;
|
||||
|
||||
var uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
||||
if (o.ambiguous) uppercaseCharSet += 'O';
|
||||
if (o.uppercase) allCharSet += uppercaseCharSet;
|
||||
|
||||
var numberCharSet = '23456789';
|
||||
if (o.ambiguous) numberCharSet += '01';
|
||||
if (o.number) allCharSet += numberCharSet;
|
||||
|
||||
var specialCharSet = '!@#$%^&*';
|
||||
if (o.special) allCharSet += specialCharSet;
|
||||
|
||||
var password = '';
|
||||
for (var m = 0; m < o.length; m++) {
|
||||
var positionChars;
|
||||
switch (positions[m]) {
|
||||
case 'l': positionChars = lowercaseCharSet; break;
|
||||
case 'u': positionChars = uppercaseCharSet; break;
|
||||
case 'n': positionChars = numberCharSet; break;
|
||||
case 's': positionChars = specialCharSet; break;
|
||||
case 'a': positionChars = allCharSet; break;
|
||||
}
|
||||
|
||||
var randomCharIndex = randomInt(0, positionChars.length - 1);
|
||||
password += positionChars.charAt(randomCharIndex);
|
||||
}
|
||||
|
||||
return password;
|
||||
};
|
||||
|
||||
// EFForg/OpenWireless
|
||||
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
||||
function randomInt(min, max) {
|
||||
var rval = 0;
|
||||
var range = max - min + 1;
|
||||
|
||||
var bits_needed = Math.ceil(Math.log2(range));
|
||||
if (bits_needed > 53) {
|
||||
throw new Exception('We cannot generate numbers larger than 53 bits.');
|
||||
}
|
||||
var bytes_needed = Math.ceil(bits_needed / 8);
|
||||
var mask = Math.pow(2, bits_needed) - 1;
|
||||
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
||||
|
||||
// Create byte array and fill with N random numbers
|
||||
var byteArray = new Uint8Array(bytes_needed);
|
||||
window.crypto.getRandomValues(byteArray);
|
||||
|
||||
var p = (bytes_needed - 1) * 8;
|
||||
for (var i = 0; i < bytes_needed; i++) {
|
||||
rval += byteArray[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
// Use & to apply the mask and reduce the number of recursive lookups
|
||||
rval = rval & mask;
|
||||
|
||||
if (rval >= range) {
|
||||
// Integer out of acceptable range
|
||||
return randomInt(min, max);
|
||||
}
|
||||
// Return an integer that falls within the range
|
||||
return min + rval;
|
||||
}
|
||||
|
||||
function extend() {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
for (var key in arguments[i]) {
|
||||
if (arguments[i].hasOwnProperty(key)) {
|
||||
arguments[0][key] = arguments[i][key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arguments[0];
|
||||
}
|
||||
|
||||
PasswordGenerationService.prototype.getOptions = function () {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
if (self.optionsCache) {
|
||||
deferred.resolve(self.optionsCache);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
chrome.storage.local.get(optionsKey, function (obj) {
|
||||
var options = obj[optionsKey];
|
||||
if (!options) {
|
||||
options = defaultOptions;
|
||||
}
|
||||
|
||||
self.optionsCache = options;
|
||||
deferred.resolve(self.optionsCache);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.saveOptions = function (options) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
var obj = {};
|
||||
obj[optionsKey] = options;
|
||||
chrome.storage.local.set(obj, function () {
|
||||
self.optionsCache = options;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// History
|
||||
|
||||
var historyKey = self.constantsService.generatedPasswordHistory;
|
||||
var maxPasswordsInHistory = 100;
|
||||
|
||||
self.utilsService.getObjFromStorage(historyKey).then(function (encrypted) {
|
||||
return decrypt(encrypted);
|
||||
}).then(function (history) {
|
||||
history.forEach(function (item) {
|
||||
self.history.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
PasswordGenerationService.prototype.getHistory = function () {
|
||||
return self.history;
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.addHistory = function (password) {
|
||||
// Prevent duplicates
|
||||
if (matchesPrevious(password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.history.push({
|
||||
password: password,
|
||||
date: Date.now()
|
||||
});
|
||||
|
||||
// Remove old items.
|
||||
if (self.history.length > maxPasswordsInHistory) {
|
||||
self.history.shift();
|
||||
}
|
||||
|
||||
save();
|
||||
};
|
||||
|
||||
PasswordGenerationService.prototype.clear = function () {
|
||||
self.history = [];
|
||||
self.utilsService.removeFromStorage(historyKey);
|
||||
};
|
||||
|
||||
function save() {
|
||||
return encryptHistory().then(function (history) {
|
||||
return self.utilsService.saveObjToStorage(historyKey, history);
|
||||
});
|
||||
}
|
||||
|
||||
function encryptHistory() {
|
||||
var promises = self.history.map(function (historyItem) {
|
||||
return self.cryptoService.encrypt(historyItem.password).then(function (encrypted) {
|
||||
return {
|
||||
password: encrypted.encryptedString,
|
||||
date: historyItem.date
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
}
|
||||
|
||||
function decrypt(history) {
|
||||
var promises = history.map(function (item) {
|
||||
return self.cryptoService.decrypt(new CipherString(item.password)).then(function (decrypted) {
|
||||
return {
|
||||
password: decrypted,
|
||||
date: item.date
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
}
|
||||
|
||||
function matchesPrevious(password) {
|
||||
var len = self.history.length;
|
||||
return len !== 0 && self.history[len - 1].password === password;
|
||||
}
|
||||
}
|
@ -8,6 +8,55 @@ const AnalyticsIds = {
|
||||
};
|
||||
|
||||
export default class UtilsService {
|
||||
static extendObject(...objects: any[]): any {
|
||||
for (let i = 1; i < objects.length; i++) {
|
||||
for (const key in objects[i]) {
|
||||
if (objects[i].hasOwnProperty(key)) {
|
||||
objects[0][key] = objects[i][key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objects[0];
|
||||
}
|
||||
|
||||
// EFForg/OpenWireless
|
||||
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
||||
static secureRandomNumber(min: number, max: number): number {
|
||||
let rval = 0;
|
||||
const range = max - min + 1;
|
||||
const bitsNeeded = Math.ceil(Math.log2(range));
|
||||
if (bitsNeeded > 53) {
|
||||
throw new Error('We cannot generate numbers larger than 53 bits.');
|
||||
}
|
||||
|
||||
const bytesNeeded = Math.ceil(bitsNeeded / 8);
|
||||
const mask = Math.pow(2, bitsNeeded) - 1;
|
||||
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
||||
|
||||
// Create byte array and fill with N random numbers
|
||||
const byteArray = new Uint8Array(bytesNeeded);
|
||||
window.crypto.getRandomValues(byteArray);
|
||||
|
||||
let p = (bytesNeeded - 1) * 8;
|
||||
for (let i = 0; i < bytesNeeded; i++) {
|
||||
rval += byteArray[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
// Use & to apply the mask and reduce the number of recursive lookups
|
||||
// tslint:disable-next-line
|
||||
rval = rval & mask;
|
||||
|
||||
if (rval >= range) {
|
||||
// Integer out of acceptable range
|
||||
return UtilsService.secureRandomNumber(min, max);
|
||||
}
|
||||
|
||||
// Return an integer that falls within the range
|
||||
return min + rval;
|
||||
}
|
||||
|
||||
static fromB64ToArray(str: string): Uint8Array {
|
||||
const binaryString = window.atob(str);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
|
Loading…
Reference in New Issue
Block a user