CryptoJS core.js source code: tags 3.1.8, 3.1.9-1, and 4.0.0

11.09.2026

CryptoJS core.js source code: tags 3.1.8, 3.1.9-1, and 4.0.0

A scientific and technical article.  The object of this study is the infrastructure module  core.js of the CryptoJS library. The subject of this research is data representation, buffering, encodings, prototype architecture, and the source of randomness, which determine the cryptographic correctness of applied JavaScript code.

Annotation

The module  core.js doesn’t implement AES, SHA-256, or HMAC as mathematical primitives, but it does define their computational environment: an object model, 32-bit word arrays, byte serialization, stream block processing, and random data generation. Therefore, its errors can compromise the security of a properly chosen primitive: by misinterpreting bytes, repeating the IV/salt, or generating an incompatible cryptographic protocol.

The key cryptanalytic fact when comparing the branches is that in 3.1.8 and 3.1.9-1, the function  WordArray.random() relies on  Math.random(), while in 4.0.0, it relies on the browser’s native cryptographic generator or Node.js. This replacement does not “strengthen AES”; it removes the predictability of the input secrets/nonces, which is the basis for practical attacks against schemes with a correct but poorly initialized cipher.

Material and methodology

The repository tags 3.1.8, 3.1.9-1, and 4.0.0 are being reviewed  brix/crypto-js. The 3.1.x branch represents the original CryptoJS, wrapped in CommonJS modules; 3.3.0 was subsequently reverted to the contents of 3.1.9-1. Version 4.0.0 introduced a change that was intentionally incompatible with some older environments: deprecating the  Math.random() CryptoJS API in favor of a native cryptographic API.

The analysis is performed at three levels: semantic (invariants  WordArray), protocol (the role of IV, salt, and nonce), and cryptanalytic (what changes with partially predictable randomness). The examples below are intended for auditing and secure migration, not for extracting other people’s keys or bypassing security.

Component Map

Category core.jsPurposeCritical invariant
UMD-wrapperExport to CommonJS, AMD, and the browser’s global scope.The same assembly should receive the expected entropy source in the target environment.
BasePseudo-class model:  extend,  create,  mixIn,  clone.Hasher and buffer states must not be implicitly shared between instances.
WordArrayStoring bytes as signed 32-bit JavaScript words.sigBytes Specifies the exact length of the message; extra bits should be removed.
C.encHex, Latin1 and UTF-8: Bytes ↔ String transitions.Cryptography is defined over bytes, not over visual text.
BufferedBlockAlgorithmAccumulation and issuance of blocks to algorithms.You cannot prematurely process the tail if a specific mode requires you to hold the block.
HasherA framework for incremental hashing and HMAC helpers.finalize() destroys the working condition; reuse requires a new copy.
WordArray.randomGetting bytes for IV, salt and other random values.For secrets and nonces, a CSPRNG is required, not a statistically “random-like” PRNG.

Version differentiation

TagRandomness model in core.jsPractical assessment
3.1.8WordArray.random() builds words from  Math.random().Do not use to generate cryptographic keys, salts, IVs, or nonces.
3.1.9-1Saves the 3.1.x model with  Math.random().Legacy compatibility does not compensate for the lack of cryptographic entropy.
4.0.0Tries to use  window.crypto.getRandomValues,  global.crypto or  require('crypto').randomBytes; throws an exception on failure.Correct migration direction; old environments without a native API may stop working.

Important.  The lack of fallback in  Math.random() 4.0.0 is a security feature. In cryptography, an error-induced failure is preferable to a “silent” transition to insecure randomness: the latter produces ciphertext that is externally indistinguishable from the correct one, but potentially vulnerable.

Cryptanalytic fact

The strength of a symmetric scheme is a property of the combination of “algorithm + mode + key + random parameters + implementation.” If  WordArray.random(16) it produces a predictable value, then AES-256 remains AES-256 as a transformation, but the design of “AES-CBC with a predictable IV” or “password encryption with a predictable salt” loses the stated protocol guarantee.

C = E_K(P \oplus IV) \quad \text{(first CBC block)}

If the IV is repeated and the first block of plaintext is the same, the equality holds:

P_1 = P’_1 \land IV = IV’ \Rightarrow C_1 = C’_1.

This doesn’t automatically reveal the entire text, but it does create an observable equality feature and facilitates message matching. A historical analog is WEP: RC4’s weaknesses, combined with IV reuse and predictable first bytes of frames, turned a large collection of intercepts into material for statistically reconstructing the keystream. The lesson for CryptoJS: the quality of the “wrapping” around a primitive is as important as the choice of the primitive itself.

K = \operatorname{PBKDF2}(\mathrm{password},\mathrm{salt},c,dkLen).

The salt in PBKDF2 doesn’t have to be secret, but it must be unique and unpredictable when a record is created. Repeating the salt allows for reuse of precomputations against identical passwords; predictability makes it easier for an attacker to prepare in advance. Modern systems require a unique salt, a sufficiently high cost  c , and preferably a memory-intensive KDF when available.

WordArrays

WordArray Stores the message as well as  words the number of significant bytes  sigBytes. The byte with the index  i is extracted from the word  words[i >>> 2] with a shift  24 - 8(i mod 4), that is, the big-endian representation of bytes within a 32-bit word is used.

b_i = (w_{\lfloor i/4 \rfloor} \mathbin{\mathtt{>>>}} (24 – 8(i \bmod 4))) \mathbin{\&} 255.

var x = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
console.log(x.toString(CryptoJS.enc.Hex)); // 000102030405

A cryptanalytic example.  The length error here is not cosmetic. If the buffer contains six significant bytes, but the developer mistakenly transmits eight, the hash, MAC, or encryption will contain two trailing bytes. In digital forensics and the verification of signed artifacts, such serialization ambiguity leads to two parties hashing different byte sequences, even though the interface displays the “same” object.

Clamp operation

The method  clamp() zeros out the insignificant bits of the last word and truncates the array. Its invariant is that after normalization, the representation contains no bytes beyond the boundary  sigBytes.

w_{\lfloor s/4 \rfloor} \leftarrow w_{\lfloor s/4 \rfloor} \mathbin{\&} (2^{32} – 1) \ll (32 – 8(s \bmod 4)), \quad s=\mathrm{sigBytes}.

var a = CryptoJS.enc.Hex.parse('a1b2c3');
a.clamp();
console.log(a.sigBytes); // 3

A historical parallel.  The class of “length-extension” attacks on Merkle–Damgård hashes demonstrates that the length and boundaries of blocks are part of the cryptographic message. While  clamp() it doesn’t eliminate the length-extension attack itself, properly accounting for the length prevents another class of implementation errors: the addition of residual data to the stream being authenticated or hashed.

Encodings and boundaries

Hex.stringify/parse Provides transparent byte mapping,  Latin1 uses 0–255 values ​​as character codes, and  Utf8 converts the JavaScript string using  encodeURIComponent/ decodeURIComponent. The input string is not “already bytes”: the same visible string can have different Unicode normalizations and, therefore, different UTF-8 sequences.

var m = CryptoJS.enc.Utf8.parse('Привет');
var h = CryptoJS.SHA256(m).toString(CryptoJS.enc.Hex);
// Для межсистемного протокола фиксируйте UTF-8 и правила нормализации явно.

A cryptanalytic example.  Protocol attacks often exploit canonicalization mismatches: the client signs one set of bytes, and the server interprets another. Similar flaws have been observed in systems where URLs, JSON, Unicode, or line breaks are normalized differently before and after MAC/signature verification. Therefore, for binary keys, IVs, salts, and ciphertext, use  WordArray,  Uint8Array, Base64, or Hex, rather than implicit text strings.

Block buffering

BufferedBlockAlgorithm Implements a streaming scheme:  _append() it converts strings to UTF-8 and adds them to a buffer, then  _process(doFlush) passes the completed blocks to a specific algorithm via  _doProcessBlock. The block size is specified in 32-bit words; the basic one  Hasher is 16 words, or 64 bytes.

n_{ready}=\begin{cases}\lceil |D|/B \rceil,&\text{with flush};\\\max(\lfloor |D|/B \rfloor-m,0),&\text{normal mode},\end{cases}

where |D| is the number of bytes in the buffer,  B is the block size in bytes,  m and is  _minBufferSize. The last parameter is important for block modes and finalization, when the final block cannot be irreversibly processed without adding padding or without authenticity checking.

var hasher = CryptoJS.algo.SHA256.create();
hasher.update('audit record: ');
hasher.update('chunk-2');
var digest = hasher.finalize().toString();

A practical example:  In log archives and network dumps, data arrives in fragments of arbitrary size. An incremental hash must yield the same digest as hashing the entire concatenation; otherwise, a “stream split dependency” error occurs, which is dangerous for backup integrity monitoring and evidentiary storage of digital artifacts.

Hasher and HMAC

Hasher defines the life cycle of  init → reset → update* → finalize; specific SHA modules implement  _doReset,  _doProcessBlock and  _doFinalize. The source code comment correctly emphasizes:  finalize() is a destructive “read-once” operation, so the object after it should not be treated as a continuation of the same computation.

\operatorname{HMAC}_H(K,M)=H((K’\oplus opad)\parallel H((K’\oplus ipad)\parallel M)).

var mac = CryptoJS.HmacSHA256(
  CryptoJS.enc.Utf8.parse('amount=1000&currency=RUB'),
  CryptoJS.enc.Hex.parse('00112233445566778899aabbccddeeff')
).toString();

A cryptanalytic example.  HMAC cannot be replaced with the construction  SHA256(key + message): for Merkle–Damgård hashes, this construction can be vulnerable to length-extension with a known digest and a guessable key length. HMAC is designed precisely to avoid this error and should be used for message authentication unless the protocol uses modern AEAD mode.

Randomness in versions

In 4.0.0, the function  cryptoSecureRandomInt() selects the first available native tool:  crypto.getRandomValues(new Uint32Array(1))[0] in the browser or  crypto.randomBytes(4).readInt32LE() in Node.js. It then  WordArray.random(nBytes) assembles 32-bit words until the required length is reached and stores the exact length in  sigBytes.

// CryptoJS 4.0.0: API библиотеки, но энтропия должна прийти из native crypto.
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(16);

Historical context.  Predictable randomness generators have already led to catastrophic consequences: a well-known case in Debian OpenSSL (2006–2008) reduced the key generation space to a practically brute-forceable limit; a separate category are the Bitcoin Android signature vulnerabilities, where insufficient nonce randomness in ECDSA allowed private keys to be recovered. These incidents relate to different codebases, but illustrate a general principle: a cryptographic algorithm does not compensate for a lack of input entropy.

\text{For ECDSA:}\quad s \equiv k^{-1}(z+rd) \pmod n.\quad \text{Repetition or predictability of }k\text{ compromises }d.

Audit conclusion.  Any call  WordArray.random() in a CryptoJS 3.1.8/3.1.9-1 application should be considered a risk indicator if the result is used as a key, salt, IV, nonce, password reset token, or session identifier. Even if the value is not secret (e.g., CBC IV), predictability or repeatability may compromise the semantic security of the mode.

Analysis of fragment 4.0.0

var crypto;
if (typeof window !== 'undefined' && window.crypto) {
  crypto = window.crypto;
}
if (!crypto && typeof window !== 'undefined' && window.msCrypto) {
  crypto = window.msCrypto;
}
if (!crypto && typeof global !== 'undefined' && global.crypto) {
  crypto = global.crypto;
}
if (!crypto && typeof require === 'function') {
  try { crypto = require('crypto'); } catch (err) {}
}

This fragment is an environment adaptation layer, not a native DRBG. Its security is inherited from the browser’s Web Crypto API or the Node.js crypto module. A modern audit should separately verify that the builder hasn’t replaced the Node module with a browser polyfill and that the target environment actually provides a system CSPRNG.

if (typeof crypto.getRandomValues === 'function') {
  return crypto.getRandomValues(new Uint32Array(1))[0];
}
if (typeof crypto.randomBytes === 'function') {
  return crypto.randomBytes(4).readInt32LE();
}
throw new Error('Native crypto module could not be used ...');

An example of a migration incident:  An old web application targeting IE 10 or a non-standard WebView may fail to generate an IV with an exception after migrating to 4.0.0. This is operationally inconvenient, but safer than generating an IV via  Math.random(). The correct solution is to upgrade the platform or explicitly provide a verified CSPRNG; don’t “fix” compatibility by returning an unsafe fallback.

Limitations of CryptoJS

The repository is marked as deprecated: further active development of CryptoJS has ceased, and the authors recommend the native Crypto module for modern browsers and Node.js. Therefore, for new systems, the Web Crypto API ( crypto.subtle,  crypto.getRandomValues) in the browser and the module  node:crypto in Node.js are preferred; CryptoJS is primarily justified for legacy compatibility and strictly limited auditable code.

In addition to the randomness generator, the audit should identify: password encryption instead of explicit KDF, CBC without authentication, ECB, nonce repetition in CTR/GCM, outdated MD5/SHA-1 for security purposes, string keys without a clear encoding specification, and a lack of error checking during decryption. Unauthenticated encryption does not ensure integrity: an attacker can modify the ciphertext, and the application can process the corrupted result.

Recommendations to the auditor

  • Inventory all CryptoJS dependencies and capture the exact tags, lock files, and delivery hashes.
  • Immediately remove  WordArray.random() from versions 3.1.8 and 3.1.9-1 security-critical data; migrate at least to an environment with a native CSPRNG.
  • Pass keys, IV, salt, and ciphertext as binary values ​​or explicitly Hex/Base64 encoded, committing to UTF-8 and canonicalization of the text.
  • Use AEAD (AES-GCM or ChaCha20-Poly1305 via modern native APIs), not “AES-CBC alone plus a homemade MAC”.
  • For passwords, use a unique salt and a modern KDF with parameters stored alongside the result; conduct cost load testing on the target infrastructure.
  • Test stream processing: the digest for a single buffer should match the digest for all splits of the same bytes.
  • Treat  finalize() as a terminal operation of the instance and create a new object for the new message.

Final Provision

The evolution  core.js from 3.1.8/3.1.9-1 to 4.0.0 is a prime example of how cryptographic security is determined not by the algorithm’s name, but by the entire implementation chain. The transition from  Math.random() a system-wide CSPRNG to a system-wide CSPRNG eliminates the fundamental flaw in the entropy source, but does not eliminate the responsibility for properly selecting the mode, managing nonces/IVs, serializing bytes, and authenticating ciphertext.

Sources

CryptoJS core.js source code: tags 3.1.8, 3.1.9-1 and 4.0.0: https://cryptrade.ru/cryptojs-core-js-source-code-tags-3-1-8-3-1-9-1-and-4-0-0/

  1. CryptoJS repository and release notes:  https://github.com/brix/crypto-js .
  2. CryptoJS  core.js, tag 4.0.0:  https://github.com/brix/crypto-js/blob/4.0.0/core.js .
  3. NIST SP 800-38A, Recommendation for Block Cipher Modes of Operation.
  4. NIST SP 800-132, Recommendation for Password-Based Key Derivation.
  5. Bellare, Canetti, Krawczyk. Keying Hash Functions for Message Authentication (HMAC).