Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
soatok committed Sep 29, 2020
0 parents commit 88cdd82
Show file tree
Hide file tree
Showing 19 changed files with 1,770 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.idea
/node_modules
/*.js
/lib/*.js
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Constant-Time JavaScript

Constant-time algorithms written in TypeScript.

[![Support on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fsoatok&style=flat)](https://patreon.com/soatok)
[![Linux Build Status](https://travis-ci.org/soatok/constant-time-js.svg?branch=master)](https://travis-ci.org/soatok/constant-time-js)
[![Latest Stable Version](https://poser.pugx.org/soatok/constant-time-js/v/stable)](https://packagist.org/packages/soatok/constant-time-js)
[![Latest Unstable Version](https://poser.pugx.org/soatok/constant-time-js/v/unstable)](https://packagist.org/packages/soatok/constant-time-js)
[![License](https://poser.pugx.org/soatok/constant-time-js/license)](https://packagist.org/packages/soatok/constant-time-js)
[![Downloads](https://img.shields.io/packagist/dt/soatok/constant-time-js.svg)](https://packagist.org/packages/soatok/constant-time-js)

**Important**: This Github repository is the companion to [Soatok's Guide to Side-Channel Attacks](https://soatok.blog/2020/08/27/soatoks-guide-to-side-channel-attacks/).

![Mind Blowing, right?](https://soatok.files.wordpress.com/2020/08/soatoktelegrams2020-01.png)

## Documentation

* `compare(a, b)` - Compare two `Uint8Array` objects.
* Returns `-1` if `a < b`
* Returns `1` if `a > b`
* Returns `0` if `a === b`
* Throws an `Error` if `a.length !== b.length`
* `compare_ints(a, b)` - Compare two integers.
* Returns `-1` if `a < b`
* Returns `1` if `a > b`
* Returns `0` if `a === b`
* `equals(a, b)` - Are these `Uint8Array` objects equal?
* Returns `true` if they are equal.
* Returns `false` if they are not equal.
* Throws an `Error` if `a.length !== b.length`
* `hmac_equals(a, b)` - Are these `Uint8Array` objects equal? (Using HMAC to compare.)
* Returns `true` if they are equal.
* Returns `false` if they are not equal.
* Throws an `Error` if `a.length !== b.length`
* `intdiv(N, D)` - Divide `N` into `D`, discarding remainder.
* Returns an integer.
* `modulo(N, D)` - Divide `N` into `D`, return the remainder.
* `resize(buf, size)` - Return a resized `Uint8Array` object (to side-step memory access leakage)
* `select(x, a, b)` - Read it as a ternary. If `x` is true, returns `a`. Otherwise, returns `b`.
* `x` must be a `boolean`
* `a` must be a `Uint8Array`
* `b` must be a `Uint8Array`
* Throws an `Error` if `a.length !== b.length`
* `select_ints(x, a, b)` - Read it as a ternary. If `x` is even, returns `a`. Otherwise, returns `b`.
(You should pass `1` or `0` for `x`).
* `x` must be a `boolean`
* `a` must be a `number`
* `b` must be a `number`
* `trim_zeroes_left(buf)`
* `buf` must be a `Uint8Array`
* Returns a `Uint8Array`
* `trim_zeroes_right(buf)`
* `buf` must be a `Uint8Array`
* Returns a `Uint8Array`
6 changes: 6 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { compare, compare_ints } from "./lib/compare";
export { equals, hmac_equals } from "./lib/equals";
export { intdiv, modulo } from './lib/intdiv';
export { resize } from './lib/resize';
export { select_ints, select } from './lib/select';
export { trim_zeroes_left, trim_zeroes_right } from './lib/trim';
21 changes: 21 additions & 0 deletions lib/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

export function compare(left: Uint8Array, right: Uint8Array): number {
if (left.length !== right.length) {
throw new Error('Both arrays must be of equal length');
}
let gt: number = 0;
let eq: number = 1;
let i: number = left.length;
while (i > 0) {
i--;
gt |= ((right[i] - left[i]) >>> 8) & eq;
eq &= ((right[i] ^ left[i]) - 1) >>> 8;
}
return (gt + gt + eq) - 1;
}

export function compare_ints(left: number, right: number): number {
let gt: number = (right - left) >>> 31;
let eq: number = ((right ^ left) - 1) >>> 31;
return (gt + gt + eq) - 1;
}
34 changes: 34 additions & 0 deletions lib/equals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as crypto from "crypto";

/**
* Constant-time buffer comparison
*
* @param {Uint8Array} left
* @param {Uint8Array} right
*/
export function equals(left: Uint8Array, right: Uint8Array): boolean {
if (left.length !== right.length) {
return false;
}
let d: number = 0;
let i: number;
for (i = 0; i < left.length; i++) {
d |= left[i] ^ right[i];
}
return d === 0;
}

/**
* Constant-time equality even with an adversarial JIT compiler.
*
* @param {Uint8Array} left
* @param {Uint8Array} right
*/
export function hmac_equals(left: Uint8Array, right: Uint8Array): boolean {
return equals(
crypto.createHmac('sha256', hmacKey).update(left).digest(),
crypto.createHmac('sha256', hmacKey).update(right).digest()
);
}

const hmacKey = crypto.randomBytes(32);
49 changes: 49 additions & 0 deletions lib/intdiv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { compare_ints } from './compare';
import { select_ints } from './select';

export function intdiv(numerator: number, denominator: number): number {
return divide(numerator, denominator)[0];
}

export function modulo(numerator: number, denominator: number): number {
return divide(numerator, denominator)[1];
}

function divide(N: number, D: number): number[] {
if (D === 0) {
throw new Error('Division by zero');
}
let Q: number = 0;
let R: number = 0;
let Qprime: number = 0;
let Rprime: number = 0;
let i: number = 0;
let compared: number;
let swap: number;
let bits: number = Math.ceil(Math.log2(N)) | 0;
for (i = 0; i <= bits; i++) {
// R := R << 1
// R(0) := N(i)
R = (R << 1) | ((N >> (bits - i)) & 1);

/*
-- if R > D then compared == 1, swap = 1
-- if R == D then compared == 0, swap = 1
-- if R < D then compared == -1, swap = 0
*/
compared = compare_ints(R, D);
swap = (1 - ((compared >>> 31) & 1));

/*
Rprime := R - D
Qprime := Q
Qprime(i) := 1
*/
Rprime = R - D;
Qprime = Q | (1 << (bits - i));

R = select_ints(swap, Rprime, R);
Q = select_ints(swap, Qprime, Q);
}
return [Q, R];
}
34 changes: 34 additions & 0 deletions lib/resize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { modulo } from './intdiv';
import { select_ints } from './select';

/**
* Iterate over every byte of the input array, returning a new object
* (so as to minimize memory access leakage outside).
*
* This is useful for building functions to trim trailing or leading zero
* bytes without leakage.
*
* @param {Uint8Array} input
* @param {number} desired
*/
export function resize(input: Uint8Array, desired: number): Uint8Array {
if (desired === 0) {
return new Uint8Array(0);
}
const output: Uint8Array = new Uint8Array(desired);
let x: number;
let y: number;
let z: number;
for (x = 0; x < input.length; x++) {
/*
if (x <= desired) y = 0;
else y = 1;
*/
y = ((x - desired) >>> 31) & 1;

z = modulo(x, desired);
output[z] = select_ints(y & 1, input[z], output[z]);
}

return output;
}
38 changes: 38 additions & 0 deletions lib/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* If TRUE, return left; else, return right.
*
* @param {boolean} returnLeft
* @param {Uint8Array} left
* @param {Uint8Array} right
* @returns {Uint8Array}
*/
export function select(returnLeft: boolean, left: Uint8Array, right: Uint8Array): Uint8Array {
if (left.length !== right.length) {
throw new Error('select() expects two Uint8Array objects of equal length');
}
/*
If returnLeft, mask = 0xFF; else, mask = 0x00;
*/
const mask: number = (-!!returnLeft) & 0xff;
const out: Uint8Array = new Uint8Array(left.length);
for (let i: number = 0; i < left.length; i++) {
out[i] = right[i] ^ ((left[i] ^ right[i]) & mask);
}
return out;
}

/**
* If TRUE, return left; else, return right.
*
* @param {number} returnLeft
* @param {number} left
* @param {number} right
* @returns {number}
*/
export function select_ints(returnLeft: number, left: number, right: number): number {
/*
If returnLeft, mask = 0xFFFFFFFF; else, mask = 0x00000000;
*/
const mask: number = (-(returnLeft & 1)) & 0xfffffffff;
return right ^ ((left ^ right) & mask);
}
32 changes: 32 additions & 0 deletions lib/trim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { resize } from './resize';

/**
* @param {Uint8Array} buf
* @returns {Uint8Array}
*/
export function trim_zeroes_right(buf: Uint8Array): Uint8Array {
let foundNonZero: number = 0;
let i: number;
let index: number = 1;
let isNonZero: number = 0;
let found: number = 0;
for (i = buf.length - 1; i >= 0; i--) {
/* if foundNonZero === 0 && buf[i] !== 0, index := i */
isNonZero = ((buf[i] - 1) >>> 8) & 0xff;
found = (foundNonZero - 1) & 0xff;
index = (i & ~foundNonZero) ^ (foundNonZero & index);
foundNonZero |= (~isNonZero) & 0xff;
}
return resize(buf, index + 1);
}

/**
* @param {Uint8Array} buf
* @returns {Uint8Array}
*/
export function trim_zeroes_left(buf: Uint8Array): Uint8Array {
buf.reverse();
buf = trim_zeroes_right(buf);
buf.reverse();
return buf;
}
Loading

0 comments on commit 88cdd82

Please sign in to comment.