Switch to SHA512 as default preferred hash algo (config.preferredHashAlgorithm)

SHA512 is usually faster than SHA256 on 64-bit platforms.

SHA-512 cannot be used with RSA/DSA keys shorter than 512-bits (which are insecure, but may
be used e.g. for testing), so error messages have been improved for this edge case.
This commit is contained in:
larabr 2024-10-28 14:09:59 +01:00
parent 12274a1543
commit fdab19dab9
5 changed files with 33 additions and 9 deletions

View File

@ -26,7 +26,7 @@ export default {
* @memberof module:config * @memberof module:config
* @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash} * @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash}
*/ */
preferredHashAlgorithm: enums.hash.sha256, preferredHashAlgorithm: enums.hash.sha512,
/** /**
* @memberof module:config * @memberof module:config
* @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric} * @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric}

View File

@ -26,6 +26,7 @@ import { uint8ArrayToB64, b64ToUint8Array } from '../../encoding/base64';
import { emsaEncode, emeEncode, emeDecode } from '../pkcs1'; import { emsaEncode, emeEncode, emeDecode } from '../pkcs1';
import enums from '../../enums'; import enums from '../../enums';
import { bigIntToNumber, bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger'; import { bigIntToNumber, bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
import hash from '../hash';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
@ -45,6 +46,14 @@ const _1n = BigInt(1);
* @async * @async
*/ */
export async function sign(hashAlgo, data, n, e, d, p, q, u, hashed) { export async function sign(hashAlgo, data, n, e, d, p, q, u, hashed) {
if (hash.getHashByteLength(hashAlgo) >= n.length) {
// Throw here instead of `emsaEncode` below, to provide a clearer and consistent error
// e.g. if a 512-bit RSA key is used with a SHA-512 digest.
// The size limit is actually slightly different but here we only care about throwing
// on common key sizes.
throw new Error('Digest size cannot exceed key modulus size');
}
if (data && !util.isStream(data)) { if (data && !util.isStream(data)) {
if (util.getWebCrypto()) { if (util.getWebCrypto()) {
try { try {
@ -264,9 +273,6 @@ async function bnSign(hashAlgo, n, d, hashed) {
n = uint8ArrayToBigInt(n); n = uint8ArrayToBigInt(n);
const m = uint8ArrayToBigInt(emsaEncode(hashAlgo, hashed, byteLength(n))); const m = uint8ArrayToBigInt(emsaEncode(hashAlgo, hashed, byteLength(n)));
d = uint8ArrayToBigInt(d); d = uint8ArrayToBigInt(d);
if (m >= n) {
throw new Error('Message size cannot exceed modulus size');
}
return bigIntToUint8Array(modExp(m, d, n), 'be', byteLength(n)); return bigIntToUint8Array(modExp(m, d, n), 'be', byteLength(n));
} }

View File

@ -121,6 +121,22 @@ export default () => describe('basic RSA cryptography', function () {
expect(util.uint8ArrayToHex(signatureNative)).to.be.equal(util.uint8ArrayToHex(signatureBN)); expect(util.uint8ArrayToHex(signatureNative)).to.be.equal(util.uint8ArrayToHex(signatureBN));
}); });
it('compare native crypto and bnSign: throw on key size shorter than digest size', async function() {
if (!detectNative()) { this.skip(); }
const bits = 512;
const hashName = 'sha512'; // digest too long for a 512-bit key
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = random.getRandomBytes(64);
const hashAlgo = openpgp.enums.write(openpgp.enums.hash, hashName);
const hashed = await crypto.hash.digest(hashAlgo, message);
enableNative();
await expect(crypto.publicKey.rsa.sign(hashAlgo, message, n, e, d, p, q, u, hashed)).to.be.rejectedWith(/Digest size cannot exceed key modulus size/);
disableNative();
await expect(crypto.publicKey.rsa.sign(hashAlgo, message, n, e, d, p, q, u, hashed)).to.be.rejectedWith(/Digest size cannot exceed key modulus size/);
});
it('compare native crypto and bnVerify', async function() { it('compare native crypto and bnVerify', async function() {
if (!detectNative()) { this.skip(); } if (!detectNative()) { this.skip(); }

View File

@ -2261,7 +2261,7 @@ function versionSpecificTests() {
]); ]);
} }
const hash = openpgp.enums.hash; const hash = openpgp.enums.hash;
expect(selfSignature.preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512, hash.sha3_256, hash.sha3_512]); expect(selfSignature.preferredHashAlgorithms).to.eql([hash.sha512, hash.sha256, hash.sha3_256, hash.sha3_512]);
const compr = openpgp.enums.compression; const compr = openpgp.enums.compression;
expect(selfSignature.preferredCompressionAlgorithms).to.eql([compr.uncompressed, compr.zlib, compr.zip]); expect(selfSignature.preferredCompressionAlgorithms).to.eql([compr.uncompressed, compr.zlib, compr.zip]);
@ -2495,7 +2495,7 @@ function versionSpecificTests() {
}); });
it('Generate RSA key - two subkeys with default values', async function() { it('Generate RSA key - two subkeys with default values', async function() {
const rsaBits = 512; const rsaBits = 1024;
const minRSABits = openpgp.config.minRSABits; const minRSABits = openpgp.config.minRSABits;
openpgp.config.minRSABits = rsaBits; openpgp.config.minRSABits = rsaBits;
@ -2601,7 +2601,7 @@ function versionSpecificTests() {
}); });
it('Generate key - override main RSA key options for subkey', async function() { it('Generate key - override main RSA key options for subkey', async function() {
const rsaBits = 512; const rsaBits = 1024;
const minRSABits = openpgp.config.minRSABits; const minRSABits = openpgp.config.minRSABits;
openpgp.config.minRSABits = rsaBits; openpgp.config.minRSABits = rsaBits;

View File

@ -1457,7 +1457,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
beforeEach(async function() { beforeEach(async function() {
minRSABitsVal = openpgp.config.minRSABits; minRSABitsVal = openpgp.config.minRSABits;
openpgp.config.minRSABits = 512; openpgp.config.minRSABits = 1024;
}); });
afterEach(function() { afterEach(function() {
@ -3845,7 +3845,9 @@ XfA3pqV4mTzF
signingKeys: privateKey_1337, signingKeys: privateKey_1337,
detached: true, detached: true,
date: past, date: past,
format: 'binary' format: 'binary',
// SHA-512 cannot be used with a 512-bit RSA key (digest too long)
config: { minRSABits: 512, preferredHashAlgorithm: openpgp.enums.hash.sha256 }
}; };
const verifyOpt = { const verifyOpt = {
message, message,