Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions src/partially_blindrsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,19 +289,40 @@ export class PartiallyBlindRSA {
algorithm: Pick<RsaHashedKeyGenParams, 'modulusLength' | 'publicExponent' | 'hash'>,
generateSafePrimeSync: (length: number) => sjcl.BigNumber | bigint = generateSafePrime,
): Promise<CryptoKeyPair> {
if (algorithm.modulusLength % 2 !== 0 || algorithm.modulusLength < 4) {
throw new Error('modulusLength must be an even number greater than or equal to 4');
}

prepare_sjcl_random_generator();

// 1. p = SafePrime(bits / 2)
// 2. q = SafePrime(bits / 2)
// 3. while p == q, go to step 2.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are now out-of-date with the implementation.

Haven't looked into detail yet, but:

I don't think this is the right place for a fix, generateSafePrime should be changed if we want to enforce a lower bound.

And if the concern is that we get more bits than requested, then that's also a problem in generateSafePrime I think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comments are still matching the spec: the loop still performs KeyGen steps 1-3. The extra check only retries when the final modulus is one bit short, which is needed to enforce the requested modulusLength.

Also, enforcing modulus length at the start seems ok as well.

// Also retry if their product is one bit short of the requested modulus length.
let p: sjcl.BigNumber;
let q: sjcl.BigNumber;
let n: sjcl.BigNumber;
const primeBitLength = algorithm.modulusLength >> 1;
const MAX_NUM_TRIES = algorithm.modulusLength ** 2;
let validKeySize = false;
let i = 0;
do {
const p_tmp = generateSafePrimeSync(algorithm.modulusLength >> 1);
const q_tmp = generateSafePrimeSync(algorithm.modulusLength >> 1);
p = typeof p_tmp === 'bigint' ? new sjcl.bn(p_tmp.toString(16)) : p_tmp;
q = typeof q_tmp === 'bigint' ? new sjcl.bn(q_tmp.toString(16)) : q_tmp;
} while (p.equals(q));
const p_tmp = generateSafePrimeSync(primeBitLength);
const q_tmp = generateSafePrimeSync(primeBitLength);
p = typeof p_tmp === 'bigint' ? new sjcl.bn('0x' + p_tmp.toString(16)) : p_tmp;
q = typeof q_tmp === 'bigint' ? new sjcl.bn('0x' + q_tmp.toString(16)) : q_tmp;
n = p.mul(q);
validKeySize =
!p.equals(q) &&
p.bitLength() === primeBitLength &&
q.bitLength() === primeBitLength &&
n.bitLength() === algorithm.modulusLength;
i++;
} while (!validKeySize && i < MAX_NUM_TRIES);

if (!validKeySize) {
throw new Error(`generateKey reached MAX_NUM_TRIES=${MAX_NUM_TRIES}`);
}

// 4. phi = (p - 1) * (q - 1)
const phi = p.sub(1).mul(q.sub(1));
Expand All @@ -318,9 +339,6 @@ export class PartiallyBlindRSA {
// TODO: replace this applying Chinese Remainder Theorem.
const d = inverseMod(e, phi);

// 7. n = p * q
const n = p.mul(q);

// 7. sk = (n, p, q, phi, d)
const sk: BigSecretKey = { n, p, q, d };
// 8. pk = (n, e)
Expand Down
7 changes: 5 additions & 2 deletions src/prime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,20 @@ export function generatePrime(bitLength: number, NUM_TRIES_PRIMALITY = 20): sjcl
const MAX_NUM_TRIES = NUM_TRIES_PRIMALITY * bitLength ** 4;

// 2^b
const twoToN = new sjcl.bn(2);
const twoToN = new sjcl.bn(1);
for (let i = 0; i < bitLength; i++) {
twoToN.doubleM();
}
twoToN.normalize();

const twoToNMinusOne = new sjcl.bn(twoToN).halveM().normalize();

let prime: sjcl.BigNumber;
let i = 0;

do {
prime = sjcl.bn.random(twoToN, SJCL_PARANOIA);
prime = sjcl.bn.random(twoToNMinusOne, SJCL_PARANOIA);
prime = prime.addM(twoToNMinusOne).normalize();
if ((prime.getLimb(0) & 0x1) == 0) {
prime = prime.addM(1).normalize();
}
Expand Down
30 changes: 30 additions & 0 deletions test/partially_blindrsa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,36 @@ test('Parameters', () => {
}
});

test.each([2, 3, 513])('generateKey/rejects invalid modulus length/%d', async (modulusLength) => {
await expect(
PartiallyBlindRSA.generateKey({
modulusLength,
publicExponent: Uint8Array.of(0x01, 0x00, 0x01),
hash: 'SHA-384',
}),
).rejects.toThrow('modulusLength must be an even number greater than or equal to 4');
});

test('generateKey/stops retrying invalid bigint safe-prime hook', async () => {
let primeCount = 0;

await expect(
PartiallyBlindRSA.generateKey(
{
modulusLength: 4,
publicExponent: Uint8Array.of(0x01, 0x00, 0x01),
hash: 'SHA-384',
},
() => {
primeCount++;
return 2n;
},
),
).rejects.toThrow('generateKey reached MAX_NUM_TRIES=16');

expect(primeCount).toBe(32);
});

describe.each(vectors)('Errors-vec%#', (v: Vector) => {
test('non-extractable-keys', async () => {
const { privateKey, publicKey } = await keysFromVector(v, false);
Expand Down
4 changes: 2 additions & 2 deletions test/primes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ test.each([128, 256, 512, 1024])(
(bitLength) => {
const p = generatePrime(bitLength);

expect(p.bitLength()).toBeGreaterThanOrEqual(bitLength);
expect(p.bitLength()).toBe(bitLength);
expect(isPrime(p)).toBe(true);
},
1_200_000,
Expand All @@ -94,7 +94,7 @@ test.each([128, 256])(
(bitLength) => {
const p = generateSafePrime(bitLength);

expect(p.bitLength()).toBeGreaterThanOrEqual(bitLength);
expect(p.bitLength()).toBe(bitLength);
expect(isSafePrime(p)).toBe(true);
},
1_200_000,
Expand Down