Initial import from hashlock
This commit is contained in:
commit
c8c957480d
|
@ -0,0 +1 @@
|
||||||
|
.config.json
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"deno.enable": true
|
||||||
|
}
|
|
@ -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");
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"preserveConstEnums": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 New Issue