Initial import from hashlock
This commit is contained in:
commit
c8c957480d
8 changed files with 690 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.config.json
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deno.enable": true
|
||||
}
|
80
hashing.test.ts
Normal file
80
hashing.test.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
|
||||
import { Hasher, HashOutputMode } from "./hashing.ts";
|
||||
|
||||
Deno.test("hasher", () => {
|
||||
assertEquals(Hasher.tagFromURL(""), null);
|
||||
assertEquals(Hasher.tagFromURL("http://"), null);
|
||||
assertEquals(Hasher.tagFromURL("http://localhost"), "localhost");
|
||||
assertEquals(Hasher.tagFromURL("http://localhost/"), "localhost");
|
||||
assertEquals(Hasher.tagFromURL("http://localhost:22000"), "localhost");
|
||||
assertEquals(Hasher.tagFromURL("http://localhost:22000/"), "localhost");
|
||||
assertEquals(
|
||||
Hasher.tagFromURL("http://localhost:22000/index.html"),
|
||||
"localhost",
|
||||
);
|
||||
assertEquals(Hasher.tagFromURL("http://website.net"), "website");
|
||||
assertEquals(Hasher.tagFromURL("http://website.net/"), "website");
|
||||
assertEquals(Hasher.tagFromURL("http://website.co.uk"), "website");
|
||||
assertEquals(Hasher.tagFromURL("http://website.co.jp/"), "website");
|
||||
assertEquals(Hasher.tagFromURL("http://www.bigpage.com"), "bigpage");
|
||||
assertEquals(Hasher.tagFromURL("https://www.securesite.com"), "securesite");
|
||||
assertEquals(
|
||||
Hasher.tagFromURL("http://domain.bigsite.com/page/path/?a=5"),
|
||||
"bigsite",
|
||||
);
|
||||
assertEquals(
|
||||
Hasher.tagFromURL("https://domains.securebigsite.com/?a=5&c=3"),
|
||||
"securebigsite",
|
||||
);
|
||||
|
||||
let hasher = new Hasher(
|
||||
"5DB6DAF0-8F1D-4FB2-9845-F3E2390FB5BB",
|
||||
"http://www.test.com/",
|
||||
);
|
||||
assertEquals(
|
||||
hasher.getHash(
|
||||
"password",
|
||||
"test",
|
||||
{ length: 12, mode: HashOutputMode.ALNUM },
|
||||
),
|
||||
"JNiDEjUp0oKs",
|
||||
);
|
||||
assertEquals(
|
||||
hasher.getHash(
|
||||
"password",
|
||||
"test",
|
||||
{ length: 16, mode: HashOutputMode.ALNUM },
|
||||
),
|
||||
"JNiDEjUp0oKsDFLi",
|
||||
);
|
||||
assertEquals(
|
||||
hasher.getHash(
|
||||
"password",
|
||||
"test",
|
||||
{ length: 12, mode: HashOutputMode.DIGITS },
|
||||
),
|
||||
"152463290522",
|
||||
);
|
||||
assertEquals(
|
||||
hasher.getHash(
|
||||
"password",
|
||||
"test",
|
||||
{ length: 12, mode: HashOutputMode.CHARS },
|
||||
),
|
||||
"JNi/'jUp0oKs",
|
||||
);
|
||||
|
||||
assertEquals(hasher.tryHashing("password"), null);
|
||||
assertEquals(hasher.tryHashing("password#"), "JNiDEjUp0oKs");
|
||||
assertEquals(hasher.tryHashing("password@othersite#"), "D7w1bBtgYcAT");
|
||||
assertEquals(hasher.tryHashing("password@othersite~d#"), "576163306621");
|
||||
assertEquals(hasher.tryHashing("password@othersite~8#"), "D7w1bBtg");
|
||||
assertEquals(hasher.tryHashing("password@othersite~14c#"), "DPw1bBtgYcAT!P");
|
||||
assertEquals(hasher.tryHashing("password~c#"), "JNi/'jUp0oKs");
|
||||
assertEquals(hasher.tryHashing("password~4#"), "JDe4");
|
||||
assertEquals(hasher.tryHashing("password~10d#"), "1524632905");
|
||||
|
||||
hasher = new Hasher("5DB6DAF0-8F1D-4FB2-9845-F3E2390FB5BB");
|
||||
assertEquals(hasher.tryHashing("password#"), null);
|
||||
assertEquals(hasher.tryHashing("password@test#"), "JNiDEjUp0oKs");
|
||||
});
|
308
hashing.ts
Normal file
308
hashing.ts
Normal file
|
@ -0,0 +1,308 @@
|
|||
import { b64_hmac_sha1 } from "./sha.ts";
|
||||
|
||||
export enum HashOutputMode {
|
||||
DIGITS = "d",
|
||||
ALNUM = "w",
|
||||
CHARS = "c",
|
||||
}
|
||||
|
||||
export type HashParams = Readonly<{
|
||||
mode: HashOutputMode;
|
||||
length: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Code responsible for the hashing of central password
|
||||
*
|
||||
* If an URL is provided, it will be used to obtain the site tag
|
||||
*/
|
||||
export class Hasher {
|
||||
private_key: string;
|
||||
site_tag: string | null;
|
||||
|
||||
constructor(private_key: string, url?: string) {
|
||||
this.private_key = private_key;
|
||||
this.site_tag = (url) ? Hasher.tagFromURL(url) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a site tag from a full URL.
|
||||
*/
|
||||
static tagFromURL(url: string): string | null {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let reg = /^https?:\/\/([^\/:]+)/;
|
||||
let match = reg.exec(url);
|
||||
if (match && match[1]) {
|
||||
let domain = match[1];
|
||||
if (domain.indexOf(".") >= 0) {
|
||||
let parts = domain.split(".");
|
||||
parts.pop();
|
||||
if (parts.length > 1 && parts[parts.length - 1] == "co") {
|
||||
parts.pop();
|
||||
}
|
||||
return parts[parts.length - 1];
|
||||
} else {
|
||||
return domain;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default params
|
||||
*/
|
||||
static getDefaultParams(): HashParams {
|
||||
return { mode: HashOutputMode.ALNUM, length: 12 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hashing params (character class and length) from a string
|
||||
*/
|
||||
static parseParams(input: string): HashParams {
|
||||
let params = this.getDefaultParams();
|
||||
|
||||
if (input.endsWith("d")) {
|
||||
params = Object.assign(params, { mode: HashOutputMode.DIGITS });
|
||||
input = input.slice(0, input.length - 1);
|
||||
} else if (input.endsWith("c")) {
|
||||
params = Object.assign(params, { mode: HashOutputMode.CHARS });
|
||||
input = input.slice(0, input.length - 1);
|
||||
} else if (input.endsWith("w")) {
|
||||
params = Object.assign(params, { mode: HashOutputMode.ALNUM });
|
||||
input = input.slice(0, input.length - 1);
|
||||
}
|
||||
|
||||
if (input) {
|
||||
params = Object.assign(params, { length: parseInt(input) });
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try hashing an input string.
|
||||
*/
|
||||
tryHashing(input: string): string | null {
|
||||
if (input.length > 1 && input.charAt(input.length - 1) == "#") {
|
||||
let base = input.substr(0, input.length - 1);
|
||||
let site_tag = this.site_tag;
|
||||
let params = Hasher.getDefaultParams();
|
||||
|
||||
if (base.indexOf("~") >= 0) {
|
||||
let smode: string;
|
||||
[base, smode] = base.split("~", 2);
|
||||
params = Hasher.parseParams(smode);
|
||||
}
|
||||
|
||||
if (base.indexOf("@") >= 0) {
|
||||
[base, site_tag] = base.split("@", 2);
|
||||
}
|
||||
|
||||
if (site_tag) {
|
||||
return this.getHash(base, site_tag, params);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a final hashed password, from a list of options.
|
||||
*/
|
||||
getHash(base: string, site: string, params: HashParams): string {
|
||||
// Hash the site name against the private key, to obtain a site key
|
||||
let site_key = this.generateHashWord(
|
||||
this.private_key,
|
||||
site,
|
||||
24,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// Hash the base password against the site key, to obtain the final key
|
||||
let final_hash = this.generateHashWord(
|
||||
site_key,
|
||||
base,
|
||||
params.length,
|
||||
true,
|
||||
params.mode == HashOutputMode.CHARS,
|
||||
true,
|
||||
params.mode != HashOutputMode.CHARS,
|
||||
params.mode == HashOutputMode.DIGITS,
|
||||
);
|
||||
|
||||
return final_hash;
|
||||
}
|
||||
|
||||
// IMPORTANT: This function should be changed carefully. It must be
|
||||
// completely deterministic and consistent between releases. Otherwise
|
||||
// users would be forced to update their passwords. In other words, the
|
||||
// algorithm must always be backward-compatible. It's only acceptable to
|
||||
// violate backward compatibility when new options are used.
|
||||
// SECURITY: The optional adjustments are positioned and calculated based
|
||||
// on the sum of all character codes in the raw hash string. So it becomes
|
||||
// far more difficult to guess the injected special characters without
|
||||
// knowing the master key.
|
||||
// TODO: Is it ok to assume ASCII is ok for adjustments?
|
||||
generateHashWord(
|
||||
siteTag: string,
|
||||
masterKey: string,
|
||||
hashWordSize: number,
|
||||
requireDigit: boolean,
|
||||
requirePunctuation: boolean,
|
||||
requireMixedCase: boolean,
|
||||
restrictSpecial: boolean,
|
||||
restrictDigits: boolean,
|
||||
) {
|
||||
// Start with the SHA1-encrypted master key/site tag.
|
||||
let s = b64_hmac_sha1(masterKey, siteTag);
|
||||
|
||||
// Use the checksum of all characters as a pseudo-randomizing seed to
|
||||
// avoid making the injected characters easy to guess. Note that it
|
||||
// isn't random in the sense of not being deterministic (i.e.
|
||||
// repeatable). Must share the same seed between all injected
|
||||
// characters so that they are guaranteed unique positions based on
|
||||
// their offsets.
|
||||
let sum = 0;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
sum += s.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Restrict digits just does a mod 10 of all the characters
|
||||
if (restrictDigits) {
|
||||
s = this.convertToDigits(s, sum, hashWordSize);
|
||||
} else {
|
||||
// Inject digit, punctuation, and mixed case as needed.
|
||||
if (requireDigit) {
|
||||
s = this.injectSpecialCharacter(s, 0, 4, sum, hashWordSize, 48, 10);
|
||||
}
|
||||
if (requirePunctuation && !restrictSpecial) {
|
||||
s = this.injectSpecialCharacter(s, 1, 4, sum, hashWordSize, 33, 15);
|
||||
}
|
||||
if (requireMixedCase) {
|
||||
s = this.injectSpecialCharacter(s, 2, 4, sum, hashWordSize, 65, 26);
|
||||
s = this.injectSpecialCharacter(s, 3, 4, sum, hashWordSize, 97, 26);
|
||||
}
|
||||
// Strip out special characters as needed.
|
||||
if (restrictSpecial) {
|
||||
s = this.removeSpecialCharacters(s, sum, hashWordSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim it to size.
|
||||
return s.substr(0, hashWordSize);
|
||||
}
|
||||
|
||||
// This is a very specialized method to inject a character chosen from a
|
||||
// range of character codes into a block at the front of a string if one of
|
||||
// those characters is not already present.
|
||||
// Parameters:
|
||||
// sInput = input string
|
||||
// offset = offset for position of injected character
|
||||
// reserved = # of offsets reserved for special characters
|
||||
// seed = seed for pseudo-randomizing the position and injected character
|
||||
// lenOut = length of head of string that will eventually survive truncation.
|
||||
// cStart = character code for first valid injected character.
|
||||
// cNum = number of valid character codes starting from cStart.
|
||||
injectSpecialCharacter(
|
||||
sInput: string,
|
||||
offset: number,
|
||||
reserved: number,
|
||||
seed: number,
|
||||
lenOut: number,
|
||||
cStart: number,
|
||||
cNum: number,
|
||||
): string {
|
||||
let pos0 = seed % lenOut;
|
||||
let pos = (pos0 + offset) % lenOut;
|
||||
// Check if a qualified character is already present
|
||||
// Write the loop so that the reserved block is ignored.
|
||||
for (let i = 0; i < lenOut - reserved; i++) {
|
||||
let i2 = (pos0 + reserved + i) % lenOut;
|
||||
let c = sInput.charCodeAt(i2);
|
||||
if (c >= cStart && c < cStart + cNum) {
|
||||
// Already present - nothing to do
|
||||
return sInput;
|
||||
}
|
||||
}
|
||||
|
||||
let sHead = (pos > 0) ? sInput.substring(0, pos) : "";
|
||||
let sInject = String.fromCharCode(
|
||||
((seed + sInput.charCodeAt(pos)) % cNum) + cStart,
|
||||
);
|
||||
let sTail = (pos + 1 < sInput.length)
|
||||
? sInput.substring(pos + 1, sInput.length)
|
||||
: "";
|
||||
|
||||
return sHead + sInject + sTail;
|
||||
}
|
||||
|
||||
// Another specialized method to replace a class of character, e.g.
|
||||
// punctuation, with plain letters and numbers.
|
||||
// Parameters:
|
||||
// sInput = input string
|
||||
// seed = seed for pseudo-randomizing the position and injected character
|
||||
// lenOut = length of head of string that will eventually survive truncation.
|
||||
removeSpecialCharacters(
|
||||
sInput: string,
|
||||
seed: number,
|
||||
lenOut: number,
|
||||
): string {
|
||||
let s = "";
|
||||
let i = 0;
|
||||
|
||||
while (i < lenOut) {
|
||||
let j = sInput.substring(i).search(/[^a-z0-9]/i);
|
||||
if (j < 0) {
|
||||
break;
|
||||
}
|
||||
if (j > 0) {
|
||||
s += sInput.substring(i, i + j);
|
||||
}
|
||||
s += String.fromCharCode((seed + i) % 26 + 65);
|
||||
i += (j + 1);
|
||||
}
|
||||
|
||||
if (i < sInput.length) {
|
||||
s += sInput.substring(i);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Convert input string to digits-only.
|
||||
// Parameters:
|
||||
// sInput = input string
|
||||
// seed = seed for pseudo-randomizing the position and injected character
|
||||
// lenOut = length of head of string that will eventually survive truncation.
|
||||
convertToDigits(sInput: string, seed: number, lenOut: number): string {
|
||||
let s = "";
|
||||
let i = 0;
|
||||
|
||||
while (i < lenOut) {
|
||||
let j = sInput.substring(i).search(/[^0-9]/i);
|
||||
if (j < 0) {
|
||||
break;
|
||||
}
|
||||
if (j > 0) {
|
||||
s += sInput.substring(i, i + j);
|
||||
}
|
||||
s += String.fromCharCode((seed + sInput.charCodeAt(i)) % 10 + 48);
|
||||
i += (j + 1);
|
||||
}
|
||||
|
||||
if (i < sInput.length) {
|
||||
s += sInput.substring(i);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
7
sha.test.ts
Normal file
7
sha.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
|
||||
import { hex_sha1 } from "./sha.ts";
|
||||
|
||||
Deno.test("sha1", () => {
|
||||
assertEquals(hex_sha1("abc"), "a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||
assertEquals(hex_sha1("test"), "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3");
|
||||
});
|
228
sha.ts
Normal file
228
sha.ts
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
|
||||
* in FIPS PUB 180-1
|
||||
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for details.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Configurable variables. You may need to tweak these to be compatible with
|
||||
* the server-side, but the defaults work in most cases.
|
||||
*/
|
||||
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
|
||||
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
|
||||
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
|
||||
|
||||
/*
|
||||
* These are the functions you'll usually want to call
|
||||
* They take string arguments and return either hex or base-64 encoded strings
|
||||
*/
|
||||
export function hex_sha1(s: string): string {
|
||||
return binb2hex(core_sha1(str2binb(s), s.length * chrsz));
|
||||
}
|
||||
export function b64_sha1(s: string): string {
|
||||
return binb2b64(core_sha1(str2binb(s), s.length * chrsz));
|
||||
}
|
||||
export function str_sha1(s: string): string {
|
||||
return binb2str(core_sha1(str2binb(s), s.length * chrsz));
|
||||
}
|
||||
export function hex_hmac_sha1(key: string, data: string): string {
|
||||
return binb2hex(core_hmac_sha1(key, data));
|
||||
}
|
||||
export function b64_hmac_sha1(key: string, data: string): string {
|
||||
return binb2b64(core_hmac_sha1(key, data));
|
||||
}
|
||||
export function str_hmac_sha1(key: string, data: string): string {
|
||||
return binb2str(core_hmac_sha1(key, data));
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the SHA-1 of an array of big-endian words, and a bit length
|
||||
*/
|
||||
function core_sha1(x: number[], len: number): number[] {
|
||||
/* append padding */
|
||||
/* SC - Get rid of warning */
|
||||
var i = (len >> 5);
|
||||
if (x[i] == undefined) {
|
||||
x[i] = 0x80 << (24 - len % 32);
|
||||
} else {
|
||||
x[i] |= 0x80 << (24 - len % 32);
|
||||
}
|
||||
/*x[len >> 5] |= 0x80 << (24 - len % 32);*/
|
||||
x[((len + 64 >> 9) << 4) + 15] = len;
|
||||
|
||||
var w = Array(80);
|
||||
var a = 1732584193;
|
||||
var b = -271733879;
|
||||
var c = -1732584194;
|
||||
var d = 271733878;
|
||||
var e = -1009589776;
|
||||
|
||||
for (var i = 0; i < x.length; i += 16) {
|
||||
var olda = a;
|
||||
var oldb = b;
|
||||
var oldc = c;
|
||||
var oldd = d;
|
||||
var olde = e;
|
||||
|
||||
for (var j = 0; j < 80; j++) {
|
||||
if (j < 16) w[j] = x[i + j];
|
||||
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
|
||||
var t = safe_add(
|
||||
safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
|
||||
safe_add(safe_add(e, w[j]), sha1_kt(j)),
|
||||
);
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = t;
|
||||
}
|
||||
|
||||
a = safe_add(a, olda);
|
||||
b = safe_add(b, oldb);
|
||||
c = safe_add(c, oldc);
|
||||
d = safe_add(d, oldd);
|
||||
e = safe_add(e, olde);
|
||||
}
|
||||
return Array(a, b, c, d, e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the appropriate triplet combination function for the current
|
||||
* iteration
|
||||
*/
|
||||
function sha1_ft(t: number, b: number, c: number, d: number): number {
|
||||
if (t < 20) return (b & c) | ((~b) & d);
|
||||
if (t < 40) return b ^ c ^ d;
|
||||
if (t < 60) return (b & c) | (b & d) | (c & d);
|
||||
return b ^ c ^ d;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the appropriate additive constant for the current iteration
|
||||
*/
|
||||
function sha1_kt(t: number): number {
|
||||
return (t < 20)
|
||||
? 1518500249
|
||||
: (t < 40)
|
||||
? 1859775393
|
||||
: (t < 60)
|
||||
? -1894007588
|
||||
: -899497514;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the HMAC-SHA1 of a key and some data
|
||||
*/
|
||||
function core_hmac_sha1(key: string, data: string): number[] {
|
||||
var bkey = str2binb(key);
|
||||
if (bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
|
||||
|
||||
var ipad = Array(16), opad = Array(16);
|
||||
for (var i = 0; i < 16; i++) {
|
||||
/* SC - Get rid of warning */
|
||||
var k = (bkey[i] != undefined ? bkey[i] : 0);
|
||||
ipad[i] = k ^ 0x36363636;
|
||||
opad[i] = k ^ 0x5C5C5C5C;
|
||||
/* ipad[i] = bkey[i] ^ 0x36363636;
|
||||
opad[i] = bkey[i] ^ 0x5C5C5C5C;*/
|
||||
}
|
||||
|
||||
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
|
||||
return core_sha1(opad.concat(hash), 512 + 160);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
|
||||
* to work around bugs in some JS interpreters.
|
||||
*/
|
||||
function safe_add(x: number, y: number) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*/
|
||||
function rol(num: number, cnt: number) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an 8-bit or 16-bit string to an array of big-endian words
|
||||
* In 8-bit function, characters >255 have their hi-byte silently ignored.
|
||||
*/
|
||||
function str2binb(str: string): number[] {
|
||||
var bin = Array();
|
||||
var mask = (1 << chrsz) - 1;
|
||||
/* SC - Get rid of warnings */
|
||||
for (var i = 0; i < str.length * chrsz; i += chrsz) {
|
||||
if (bin[i >> 5] != undefined) {
|
||||
bin[i >> 5] |=
|
||||
(str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i % 32);
|
||||
} else {
|
||||
bin[i >> 5] = (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i % 32);
|
||||
}
|
||||
}
|
||||
/*for(var i = 0; i < str.length * chrsz; i += chrsz)
|
||||
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);*/
|
||||
return bin;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a string
|
||||
*/
|
||||
function binb2str(bin: number[]): string {
|
||||
var str = "";
|
||||
var mask = (1 << chrsz) - 1;
|
||||
for (var i = 0; i < bin.length * 32; i += chrsz) {
|
||||
str += String.fromCharCode((bin[i >> 5] >>> (32 - chrsz - i % 32)) & mask);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a hex string.
|
||||
*/
|
||||
function binb2hex(binarray: number[]): string {
|
||||
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
var str = "";
|
||||
for (var i = 0; i < binarray.length * 4; i++) {
|
||||
str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
|
||||
hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a base-64 string
|
||||
*/
|
||||
function binb2b64(binarray: number[]): string {
|
||||
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
var str = "";
|
||||
for (var i = 0; i < binarray.length * 4; i += 3) {
|
||||
/* SC - Get rid of warning */
|
||||
var b1 = binarray[i >> 2] != undefined
|
||||
? ((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16
|
||||
: 0;
|
||||
var b2 = binarray[i + 1 >> 2] != undefined
|
||||
? ((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8
|
||||
: 0;
|
||||
var b3 = binarray[i + 2 >> 2] != undefined
|
||||
? ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF)
|
||||
: 0;
|
||||
var triplet = b1 | b2 | b3;
|
||||
/*var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
|
||||
| (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
|
||||
| ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);*/
|
||||
for (var j = 0; j < 4; j++) {
|
||||
if (i * 8 + j * 6 > binarray.length * 32) str += b64pad;
|
||||
else str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"preserveConstEnums": true
|
||||
}
|
||||
}
|
55
ui/zenity.ts
Normal file
55
ui/zenity.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
// UI based on zenity dialogs
|
||||
|
||||
import { Hasher } from "../hashing.ts";
|
||||
|
||||
async function askInput(label: string, password = false): Promise<string> {
|
||||
const process = Deno.run({
|
||||
cmd: ["zenity", password ? "--password" : "--entry", `--text=${label}`],
|
||||
stdout: "piped",
|
||||
});
|
||||
const output = await process.output();
|
||||
const result = new TextDecoder().decode(output).replace(/[ \n]+$/, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
async function showMessage(message: string): Promise<void> {
|
||||
const process = Deno.run({
|
||||
cmd: ["zenity", "--info", `--text=${message}`],
|
||||
});
|
||||
await process.status();
|
||||
}
|
||||
|
||||
async function copyToClipboard(text: string): Promise<void> {
|
||||
const process = Deno.run({
|
||||
cmd: ["xclip"],
|
||||
stdin: "piped",
|
||||
});
|
||||
if (process.stdin) {
|
||||
await Deno.writeAll(process.stdin, new TextEncoder().encode(text));
|
||||
process.stdin.close();
|
||||
}
|
||||
await process.status();
|
||||
}
|
||||
|
||||
async function readPrivateKey(): Promise<string> {
|
||||
const content = await Deno.readTextFile(".config.json");
|
||||
const config = JSON.parse(content);
|
||||
return config.privateKey;
|
||||
}
|
||||
|
||||
const privateKey = await readPrivateKey();
|
||||
const siteTag = await askInput("site tag:");
|
||||
let password = await askInput("password:", true);
|
||||
if (password.slice(password.length - 1) != "#") {
|
||||
password += "#";
|
||||
}
|
||||
|
||||
const hasher = new Hasher(privateKey);
|
||||
hasher.site_tag = siteTag;
|
||||
const result = hasher.tryHashing(password);
|
||||
if (result) {
|
||||
await copyToClipboard(result);
|
||||
await showMessage(`Your hashed password (copied to clipboard): ${result}`);
|
||||
} else {
|
||||
await showMessage(`Error in hashing password`);
|
||||
}
|
Loading…
Reference in a new issue