Crypto-View

digitalbazaar/forge

Cryptographic posture

96 cryptographic locations: 34 already broken, 43 quantum-vulnerable, 6 reduced-margin, 10 undetermined, 3 quantum-safe

67 establish keys, so traffic protected by them and recorded today becomes readable once the algorithm falls. 130 files analysed.

Quantum-vulnerable 43 Already broken 34 Reduced margin 6 Could not be determined 10 Quantum-safe 3
To address80
Key establishment67
Inventory only0
Total findings96
What was analysed
Branch main
Commit 7a43db987bd0ecdc5b41f6d73f58ba6ca5bf9ae1 Start 1.4.1-0.
Committed 2026-03-24 21:44 UTC
Scanned 2026-09-21 10:58 UTC 9 hours ago
Coverage 130 files, 1 python, 113 javascript

List of cryptographic assets

RSAES-PKCS1v15 Already broken Recorded traffic 34 places See details

TLS cipher suite named in source

A cipher suite written into the code rather than into a configuration file - `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`, or the OpenSSL spelling `ECDHE-RSA-AES128-GCM-SHA256`. The key exchange is reported, and the bulk cipher separately when it is one of the broken ones.

This code names the exact cryptography its TLS connections may use. Because the list is in the program rather than in a settings file, changing it needs a new release - which is the thing that makes a migration slow.

What to do. A hardcoded suite list ships with the binary and cannot be changed without a release, so move it to configuration first. The key exchange changes when the TLS library offers a hybrid group, not before.

  1. README.md:328 forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  2. README.md:329 forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  3. README.md:403 forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  4. README.md:404 forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  5. examples/tls.js:86 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  6. examples/tls.js:87 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  7. examples/tls.js:145 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  8. examples/tls.js:146 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  9. lib/aesCipherSuites.js:18 tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = {
  10. lib/aesCipherSuites.js:20 name: 'TLS_RSA_WITH_AES_128_CBC_SHA',
  11. lib/aesCipherSuites.js:34 tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
  12. lib/aesCipherSuites.js:36 name: 'TLS_RSA_WITH_AES_256_CBC_SHA',
  13. tests/legacy/common.js:1730 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  14. tests/legacy/common.js:1731 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  15. tests/legacy/common.js:1801 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  16. tests/legacy/common.js:1802 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  17. tests/legacy/common.js:1885 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  18. tests/legacy/common.js:1886 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  19. tests/legacy/common.js:1957 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  20. tests/legacy/common.js:1958 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  21. tests/legacy/common.js:2054 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  22. tests/legacy/common.js:2055 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  23. tests/legacy/common.js:2136 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  24. tests/legacy/common.js:2137 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  25. tests/legacy/ws-webid.js:55 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  26. tests/legacy/ws-webid.js:56 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  27. tests/unit/tls.js:40 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  28. tests/unit/tls.js:41 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  29. tests/unit/tls.js:81 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  30. tests/unit/tls.js:82 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  31. tests/websockets/server-webid.js:419 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  32. tests/websockets/server-webid.js:420 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
  33. tests/websockets/server-ws.js:85 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
  34. tests/websockets/server-ws.js:86 test path forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
config.cipher-suite · CWE-757
RSA Quantum-vulnerable Recorded traffic 20 places See details

Cryptography through node-forge

`forge.pki.rsa.generateKeyPair()` or another node-forge primitive. node-forge implements RSA and X.509 in JavaScript and has no post-quantum algorithms.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. Move the operation to a runtime that offers ML-KEM and ML-DSA.

  1. examples/create-cert.js:4 test path var keys = forge.pki.rsa.generateKeyPair(2048);
  2. examples/create-cert.js:8 test path var cert = forge.pki.createCertificate();
  3. examples/create-csr.js:4 test path var keys = forge.pki.rsa.generateKeyPair(2048);
  4. examples/create-pkcs12.js:6 test path var keys = forge.pki.rsa.generateKeyPair(2048);
  5. examples/create-pkcs12.js:11 test path var cert = forge.pki.createCertificate();
  6. examples/sign-p7.js:46 test path var keys = forge.pki.rsa.generateKeyPair(2048);
  7. examples/sign-p7.js:65 test path var cert = forge.pki.createCertificate();
  8. examples/tls.js:7 test path var keys = forge.pki.rsa.generateKeyPair(512);
  9. examples/tls.js:10 test path var cert = forge.pki.createCertificate();
  10. tests/legacy/common.js:101 test path var keys = forge.pki.rsa.generateKeyPair(bits);
  11. tests/legacy/common.js:138 test path var keys = forge.pki.rsa.generateKeyPair(bits);
  12. tests/legacy/common.js:139 test path var cert = forge.pki.createCertificate();
  13. tests/legacy/common.js:1599 test path var cert = forge.pki.createCertificate();
  14. tests/legacy/webid.js:53 test path var cert = forge.pki.createCertificate();
  15. tests/unit/tls.js:121 test path var keys = forge.pki.rsa.generateKeyPair(512);
  16. tests/unit/tls.js:122 test path var cert = forge.pki.createCertificate();
  17. tests/websockets/server-webid.js:297 test path var keys = forge.pki.rsa.generateKeyPair(512);
  18. tests/websockets/server-webid.js:300 test path var cert = forge.pki.createCertificate();
  19. tests/websockets/server-ws.js:9 test path var keys = forge.pki.rsa.generateKeyPair(512);
  20. tests/websockets/server-ws.js:12 test path var cert = forge.pki.createCertificate();
js.forge · CWE-327
RSASSA-PKCS1v15 Quantum-vulnerable 6 places See details

SSH algorithms pinned in configuration

A `KexAlgorithms`, `HostKeyAlgorithms` or `Ciphers` directive in an SSH configuration. `KexAlgorithms` is the line that decides whether recorded sessions stay confidential.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer. The v1.5 signature scheme of RFC 8017 section 8.2 has no classical break of its own; RSA-PSS is preferred for new work, but the quantum exposure is the same for both.

What to do. Put `sntrup761x25519-sha512@openssh.com` first in `KexAlgorithms`. It is a hybrid, so it is no weaker than the classical exchange it replaces.

  1. lib/ssh.js:32 var algorithm = 'ssh-rsa';
  2. lib/ssh.js:128 var type = 'ssh-rsa';
  3. lib/ssh.js:172 var type = 'ssh-rsa';
  4. tests/unit/ssh.js:42 test path 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVD' +
  5. tests/unit/ssh.js:55 test path 'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
  6. tests/unit/ssh.js:87 test path 'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
config.ssh-algorithms · CWE-757
RSA Quantum-vulnerable Recorded traffic 4 places See details

One-shot signing or public-key encryption

`crypto.sign()`, `crypto.verify()`, `privateEncrypt()`, `publicDecrypt()`, `privateDecrypt()` or `publicEncrypt()`. These take the key at runtime, so the algorithm follows the key; every key type Node accepts here is classical.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. ML-KEM-768 for encryption, ML-DSA-65 for signatures.

  1. tests/pocs/ghsa-ppp5-5v6c-4jwp.js:141 test path const controlSig = crypto.sign('sha256', controlMsg, {
  2. tests/pocs/ghsa-ppp5-5v6c-4jwp.js:151 test path const controlNode = crypto.verify('sha256', controlMsg, {
  3. tests/pocs/ghsa-ppp5-5v6c-4jwp.js:155 test path const forgedNode = crypto.verify('sha256', msg, {
  4. tests/pocs/ghsa-q67f-28xg-22rw.js:61 test path const ok = crypto.verify(null, message, keyObject, signature);
js.node.oneshot · CWE-327
RSA-OAEP Quantum-vulnerable Recorded traffic 4 places See details

JOSE algorithm declared in configuration

An `alg` value in JSON or YAML. `none` is reported as a critical defect; HS* is an HMAC and is not quantum-vulnerable; RS*, PS*, ES* and EdDSA are.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. No standardised post-quantum JOSE algorithm exists yet. Keep token lifetimes short.

  1. lib/rsa.js:1087 } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
  2. lib/rsa.js:1286 } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
  3. tests/unit/pkcs1.js:1036 test path ciphertext = publicKey.encrypt(message, 'RSA-OAEP', {md: md});
  4. tests/unit/pkcs1.js:1037 test path decoded = privateKey.decrypt(ciphertext, 'RSA-OAEP', {md: md});
jose.algorithm · CWE-327
RSA Quantum-vulnerable Recorded traffic 2 places See details

Classical key pair generated with Node crypto

`crypto.generateKeyPair()` or its synchronous form. The detector resolves the algorithm through a `promisify()` wrapper and through an import alias, both of which are common enough that missing them would leave real key generation unreported.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. ML-KEM-768 for encryption, ML-DSA-65 for signatures. Node exposes neither yet, so this usually means an addon or a service boundary.

  1. lib/rsa.js:934 return _crypto.generateKeyPair('rsa', {
  2. lib/rsa.js:1014 var keypair = _crypto.generateKeyPairSync('rsa', {
js.generatekeypair · CWE-327
RSASSA-PKCS1v15 Quantum-vulnerable 2 places See details

Classical key generated with WebCrypto

`subtle.generateKey({name: …})` naming RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDSA or ECDH. The algorithm name is taken from the object literal.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer. The v1.5 signature scheme of RFC 8017 section 8.2 has no classical break of its own; RSA-PSS is preferred for new work, but the quantum exposure is the same for both.

What to do. No post-quantum algorithm is in the WebCrypto specification yet. Where the key protects long-lived data, move that operation server-side.

  1. lib/rsa.js:959 name: 'RSASSA-PKCS1-v1_5',
  2. lib/rsa.js:984 name: 'RSASSA-PKCS1-v1_5',
js.webcrypto.generatekey · CWE-327
ECDH Quantum-vulnerable Recorded traffic 1 place See details

SSH algorithms pinned in configuration

A `KexAlgorithms`, `HostKeyAlgorithms` or `Ciphers` directive in an SSH configuration. `KexAlgorithms` is the line that decides whether recorded sessions stay confidential.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. Put `sntrup761x25519-sha512@openssh.com` first in `KexAlgorithms`. It is a hybrid, so it is no weaker than the classical exchange it replaces.

config.ssh-algorithms · CWE-757
RSA4096-bit Quantum-vulnerable Recorded traffic 1 place See details

Classical key pair generated with Node crypto

`crypto.generateKeyPair()` or its synchronous form. The detector resolves the algorithm through a `promisify()` wrapper and through an import alias, both of which are common enough that missing them would leave real key generation unreported.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. ML-KEM-768 for encryption, ML-DSA-65 for signatures. Node exposes neither yet, so this usually means an addon or a service boundary.

  1. tests/pocs/ghsa-ppp5-5v6c-4jwp.js:80 test path const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
js.generatekeypair · CWE-327
RSA2048-bit Quantum-vulnerable 1 place See details

X.509 certificate

A PEM certificate. The subject public-key algorithm and key size are read from the SubjectPublicKeyInfo structure, and the not-after date is reported so certificates that outlive the migration window are visible.

This is a digital identity document. Its expiry date matters: one valid for many years locks in today's algorithms for that long.

What to do. Shorten certificate lifetimes so re-issuing with a post-quantum key is routine.

  1. tests/server.crt:1 test path -----BEGIN CERTIFICATE-----
pem.certificate
RSA Quantum-vulnerable Recorded traffic 1 place See details

Private key committed to the repository

A PEM private-key block. The header names the algorithm where the format is the legacy one; for PKCS#8 the algorithm OID is read out of the DER. A private key in version control is a present-day incident before it is a quantum question.

A secret key is stored in this repository's files. Anyone who can read the repository can use it. This needs attention today, regardless of quantum computers.

What to do. Revoke and rotate the key, then keep key material out of the repository. Record the algorithm in the inventory.

  1. tests/server.key:1 test path -----BEGIN RSA PRIVATE KEY-----
pem.private-key · CWE-321, CWE-327
RSA Quantum-vulnerable 1 place See details

SSH public key

An `ssh-rsa`, `ecdsa-sha2-*`, `ssh-ed25519` or `ssh-dss` key. `ssh-dss` is disabled by default in current OpenSSH and is reported as broken.

A quantum computer of sufficient size breaks this completely. It has to be replaced, not tuned. Broken by Shor's algorithm on a cryptographically relevant quantum computer.

What to do. Replace `ssh-dss` now. For the rest, enable the `sntrup761x25519-sha512` key exchange, which protects recorded sessions even while host keys stay classical.

  1. tests/unit/ssh.js:42 test path 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVD' +
ssh.public-key · CWE-327
PBKDF2 Reduced margin 5 places See details

Password hashing or key derivation

A password hashing or key derivation function. Not a quantum exposure, and part of the inventory: a cryptographic bill of materials that omits how passwords are stored is not complete.

This is how passwords are stored. Quantum computers do not meaningfully weaken it.

What to do. Argon2id for new work. PBKDF2 needs a high iteration count to stay defensible.

  1. lib/pbe.js:273 var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen, md);
  2. lib/pbkdf2.js:63 return crypto.pbkdf2(p, s, c, dkLen, function(err, key) {
  3. tests/benchmarks/so-44303784.js:13 test path const key = forge.pkcs5.pbkdf2(pwd, salt, 100, 16);
  4. tests/issues/issue-428.js:247 test path var derive = forge.pkcs5.pbkdf2('password', salt, rounds, 16, hashId);
  5. tests/legacy/common.js:665 test path var dk = forge.pkcs5.pbkdf2('password', 'salt', 1, 20);
js.kdf
SHA-256 Reduced margin 1 place See details

Hash function in use

`createHash()` or `subtle.digest()`. The digest is read from the argument, so one rule covers the family: MD5 and SHA-1 are reported as broken, SHA-256 as a reduced margin, SHA-384 and above as inventory.

A quantum computer weakens this but does not break it. Increasing the key or digest size restores the margin. Pre-image resistance falls to about 128 bits of quantum work. Adequate for most uses; SHA-384 restores the full margin where a signature must last decades.

What to do. SHA-256 as the floor, SHA-384 where the digest protects something long-lived.

  1. tests/pocs/ghsa-ppp5-5v6c-4jwp.js:94 test path const digest = crypto.createHash('sha256').update(msg).digest();
js.hash · CWE-328
unknown Could not be determined 3 places See details

Key pair algorithm chosen at runtime

`generateKeyPair(x, …)` where the first argument is an identifier rather than a literal. Reported as unresolved rather than assumed.

This code picks its encryption method while it runs. A scan cannot tell which one it ends up using.

What to do. Trace the value to its configuration source and record the concrete algorithm by hand.

  1. tests/legacy/common.js:101 test path var keys = forge.pki.rsa.generateKeyPair(bits);
  2. tests/legacy/common.js:138 test path var keys = forge.pki.rsa.generateKeyPair(bits);
  3. tests/unit/rsa.js:124 test path RSA.generateKeyPair(genOptions, function(err, pair) {
js.generatekeypair.variable · CWE-327
CSPRNG Quantum-safe 2 places See details

Random number generation

`crypto.randomBytes()`, `randomUUID()` or `getRandomValues()`. A platform CSPRNG is not a quantum exposure; it is recorded because an inventory that omits the randomness source is incomplete. `Math.random()` is a separate, present-day defect.

This is where the software gets its random numbers. Quantum computers do not weaken it.

What to do. No action.

  1. lib/prng.js:274 return _crypto.getRandomValues(arr);
  2. lib/random.js:122 return _crypto.getRandomValues(arr);
js.rng
bcrypt Quantum-safe 1 place See details

Password hashing or key derivation

A password hashing or key derivation function. Not a quantum exposure, and part of the inventory: a cryptographic bill of materials that omits how passwords are stored is not complete.

This is how passwords are stored. Quantum computers do not meaningfully weaken it.

What to do. Argon2id for new work. PBKDF2 needs a high iteration count to stay defensible.

  1. lib/pbkdf2.js:58 return crypto.pbkdf2Sync(p, s, c, dkLen).toString('binary');
js.kdf

Cryptographic assets

Algorithm Assessment What it means Occurrences
RSAES-PKCS1v15 Already broken PKCS#1 v1.5 encryption padding is vulnerable to Bleichenbacher oracles today, and the underlying RSA is broken by Shor. 34
RSA Quantum-vulnerable Broken by Shor's algorithm on a cryptographically relevant quantum computer. 30
unknown Could not be determined The algorithm could not be established from the source - chosen at runtime, or decided somewhere this scan does not reach. 10
RSASSA-PKCS1v15 Quantum-vulnerable Broken by Shor's algorithm on a cryptographically relevant quantum computer. The v1.5 signature scheme of RFC 8017 section 8.2 has no classical break of its own; RSA-PSS is preferred for new work, but the quantum exposure is the same for both. 8
PBKDF2 Reduced margin Not broken by a quantum computer, but weak against modern GPU cracking at low iteration counts, which is a present-day concern. 5
RSA-OAEP Quantum-vulnerable Broken by Shor's algorithm on a cryptographically relevant quantum computer. 4
CSPRNG Quantum-safe A cryptographically secure random number generator provided by the platform. Not weakened by a quantum computer. 2
ECDH Quantum-vulnerable Broken by Shor's algorithm on a cryptographically relevant quantum computer. 1
bcrypt Quantum-safe A password hashing function. Grover offers only a marginal speed-up against a deliberately slow function, so quantum computing is not the concern here. 1
SHA-256 Reduced margin Pre-image resistance falls to about 128 bits of quantum work. Adequate for most uses; SHA-384 restores the full margin where a signature must last decades. 1

Imported cryptographic libraries

Library Files
crypto the Node crypto module 7