Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90884e6792 | ||
|
|
2a801f62c5 | ||
|
|
31d827d25a | ||
|
|
ba083107c6 | ||
|
|
38b74c03c3 | ||
|
|
1d303185c0 | ||
|
|
b60b80ca6b | ||
|
|
61b6a4e7ba | ||
|
|
78bde5f9f3 | ||
|
|
409ae7696f | ||
|
|
1033040187 | ||
|
|
7f809c93bd | ||
|
|
ac28862144 | ||
|
|
4d5da7a916 | ||
|
|
3cbd6c1020 | ||
|
|
20b827da1b | ||
|
|
0241952f57 | ||
|
|
8623234c15 | ||
|
|
7e2710a14c | ||
|
|
e4ac157d8b | ||
|
|
3b4c51fc42 | ||
|
|
9d1a45024f | ||
|
|
2d9604ecde | ||
|
|
77d1bddbe7 | ||
|
|
b969c4bbcd | ||
|
|
73ba314469 | ||
|
|
1e821e2030 | ||
|
|
793d65c12f | ||
|
|
eb61233396 | ||
|
|
b405ff597f | ||
|
|
ec5a222212 | ||
|
|
150d88df67 | ||
|
|
51bb88ea5e | ||
|
|
8e6d15bf2e | ||
|
|
f1118ba5ba | ||
|
|
25a323885c | ||
|
|
cfe95b924e | ||
|
|
2436c1af3d | ||
|
|
57e3110669 | ||
|
|
89ce2460ab | ||
|
|
70f725bee5 | ||
|
|
19e7ca77b7 | ||
|
|
cb6e5657a1 | ||
|
|
4b7673ebff | ||
|
|
52f7128c0b | ||
|
|
31efd5b53f | ||
|
|
3e4f8f6b8a | ||
|
|
7e4cf2f308 | ||
|
|
918e68038e | ||
|
|
64b5a3297b | ||
|
|
9d7a54cd59 | ||
|
|
3b7e884007 | ||
|
|
73c20dd8e0 | ||
|
|
88c99ab9ee | ||
|
|
5bfe6d5b9b | ||
|
|
4338ce049f |
@@ -1,16 +1,33 @@
|
||||
{
|
||||
"bitwise" : false, // Prohibits the use of bitwise operators (not confuse & with &&)
|
||||
"curly" : true, // Requires to always put curly braces around blocks in loops and conditionals
|
||||
"eqeqeq" : false, // Prohibits the use of == and != in favor of === and !==
|
||||
"eqnull" : true, // Suppresses warnings about == null comparisons
|
||||
"immed" : true, // Requires immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||
"latedef" : true, // Prohibits the use of a variable before it was defined
|
||||
"newcap" : false, // Requires to capitalize names of constructor functions
|
||||
"noarg" : true, // Prohibits the use of arguments.caller and arguments.callee
|
||||
"strict" : false, // Requires all functions to run in ECMAScript 5's strict mode
|
||||
"undef" : true, // Require non-global variables to be declared (prevents global leaks)
|
||||
"asi" : true, // Suppresses warnings about missing semicolons
|
||||
"globals": {
|
||||
"CryptoJS": true
|
||||
}
|
||||
"bitwise": false, // Prohibits the use of bitwise operators (not confuse & with &&)
|
||||
"curly": true, // Requires to always put curly braces around blocks in loops and conditionals
|
||||
"eqeqeq": false, // Prohibits the use of == and != in favor of === and !==
|
||||
"eqnull": true, // Suppresses warnings about == null comparisons
|
||||
"immed": true, // Requires immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||
"latedef": false, // Prohibits the use of a variable before it was defined
|
||||
"newcap": false, // Requires to capitalize names of constructor functions
|
||||
"noarg": true, // Prohibits the use of arguments.caller and arguments.callee
|
||||
"strict": false, // Requires all functions to run in ECMAScript 5's strict mode
|
||||
"undef": true, // Require non-global variables to be declared (prevents global leaks)
|
||||
"asi": true, // Suppresses warnings about missing semicolons
|
||||
"funcscope": false,
|
||||
"shadow": true,
|
||||
"expr": true,
|
||||
"-W041": true,
|
||||
"-W018": true,
|
||||
"globals": {
|
||||
"CryptoJS": true,
|
||||
"escape": true,
|
||||
"unescape": true,
|
||||
"Int8Array": true,
|
||||
"Int16Array": true,
|
||||
"Int32Array": true,
|
||||
"Uint8Array": true,
|
||||
"Uint16Array": true,
|
||||
"Uint32Array": true,
|
||||
"Uint8ClampedArray": true,
|
||||
"ArrayBuffer": true,
|
||||
"Float32Array": true,
|
||||
"Float64Array": true
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
before_script:
|
||||
- npm install -g grunt-cli
|
||||
- npm install build
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- "node_modules"
|
||||
@@ -29,6 +29,8 @@ module.exports = function (grunt) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Will load the custom tasks
|
||||
grunt.loadTasks('./grunt/tasks');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# crypto-js
|
||||
# crypto-js [](https://travis-ci.org/brix/crypto-js)
|
||||
|
||||
JavaScript library of crypto standards.
|
||||
|
||||
@@ -15,6 +15,18 @@ npm install crypto-js
|
||||
|
||||
### Usage
|
||||
|
||||
ES6 import for typical API call signing use case:
|
||||
|
||||
```javascript
|
||||
import sha256 from 'crypto-js/sha256';
|
||||
import hmacSHA512 from 'crypto-js/hmac-sha512';
|
||||
import Base64 from 'crypto-js/enc-base64';
|
||||
|
||||
const message, nonce, path, privateKey; // ...
|
||||
const hashDigest = sha256(nonce + message);
|
||||
const hmacDigest = Base64.stringify(hmacSHA512(path + hashDigest, privateKey));
|
||||
```
|
||||
|
||||
Modular include:
|
||||
|
||||
```javascript
|
||||
@@ -89,7 +101,7 @@ require(["crypto-js"], function (CryptoJS) {
|
||||
|
||||
## API
|
||||
|
||||
See: https://code.google.com/p/crypto-js
|
||||
See: https://cryptojs.gitbook.io/docs/
|
||||
|
||||
### AES Encryption
|
||||
|
||||
@@ -99,13 +111,13 @@ See: https://code.google.com/p/crypto-js
|
||||
var CryptoJS = require("crypto-js");
|
||||
|
||||
// Encrypt
|
||||
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123');
|
||||
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
|
||||
|
||||
// Decrypt
|
||||
var bytes = CryptoJS.AES.decrypt(ciphertext.toString(), 'secret key 123');
|
||||
var plaintext = bytes.toString(CryptoJS.enc.Utf8);
|
||||
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
|
||||
var originalText = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
console.log(plaintext);
|
||||
console.log(originalText); // 'my message'
|
||||
```
|
||||
|
||||
#### Object encryption
|
||||
@@ -116,13 +128,13 @@ var CryptoJS = require("crypto-js");
|
||||
var data = [{id: 1}, {id: 2}]
|
||||
|
||||
// Encrypt
|
||||
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123');
|
||||
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123').toString();
|
||||
|
||||
// Decrypt
|
||||
var bytes = CryptoJS.AES.decrypt(ciphertext.toString(), 'secret key 123');
|
||||
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
|
||||
var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
|
||||
|
||||
console.log(decryptedData);
|
||||
console.log(decryptedData); // [{id: 1}, {id: 2}]
|
||||
```
|
||||
|
||||
### List of modules
|
||||
@@ -196,3 +208,42 @@ console.log(decryptedData);
|
||||
- ```crypto-js/pad-iso97971```
|
||||
- ```crypto-js/pad-zeropadding```
|
||||
- ```crypto-js/pad-nopadding```
|
||||
|
||||
|
||||
## Release notes
|
||||
|
||||
### 4.0.0
|
||||
|
||||
This is an update including breaking changes for some environments.
|
||||
|
||||
In this version `Math.random()` has been replaced by the random methods of the native crypto module.
|
||||
|
||||
For this reason CryptoJS might not run in some JavaScript environments without native crypto module. Such as IE 10 or before or React Native.
|
||||
|
||||
### 3.3.0
|
||||
|
||||
Rollback, `3.3.0` is the same as `3.1.9-1`.
|
||||
|
||||
The move of using native secure crypto module will be shifted to a new `4.x.x` version. As it is a breaking change the impact is too big for a minor release.
|
||||
|
||||
### 3.2.1
|
||||
|
||||
The usage of the native crypto module has been fixed. The import and access of the native crypto module has been improved.
|
||||
|
||||
### 3.2.0
|
||||
|
||||
In this version `Math.random()` has been replaced by the random methods of the native crypto module.
|
||||
|
||||
For this reason CryptoJS might does not run in some JavaScript environments without native crypto module. Such as IE 10 or before.
|
||||
|
||||
If it's absolute required to run CryptoJS in such an environment, stay with `3.1.x` version. Encrypting and decrypting stays compatible. But keep in mind `3.1.x` versions still use `Math.random()` which is cryptographically not secure, as it's not random enough.
|
||||
|
||||
This version came along with `CRITICAL` `BUG`.
|
||||
|
||||
DO NOT USE THIS VERSION! Please, go for a newer version!
|
||||
|
||||
### 3.1.x
|
||||
|
||||
The `3.1.x` are based on the original CryptoJS, wrapped in CommonJS modules.
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
module.exports = {
|
||||
dev: {
|
||||
options: {
|
||||
jshintrc: true
|
||||
jshintrc: process.cwd() + '/.jshintrc',
|
||||
reporterOutput: ''
|
||||
},
|
||||
files: {
|
||||
src: [
|
||||
|
||||
+54
-55
@@ -1,8 +1,9 @@
|
||||
/*jshint node: true*/
|
||||
/*jshint esversion: 6*/
|
||||
|
||||
var _ = require("lodash"),
|
||||
|
||||
fmd = require("fmd");
|
||||
fmd = require("fmd");
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
@@ -14,78 +15,76 @@ module.exports = function (grunt) {
|
||||
modules = {},
|
||||
|
||||
config = {
|
||||
target: this.target + '/',
|
||||
factories: ["commonjs", "amd", "global"],
|
||||
trim_whitespace: true,
|
||||
new_line: "unix",
|
||||
indent: "\t"
|
||||
};
|
||||
target: this.target + '/',
|
||||
factories: ["commonjs", "amd", "global"],
|
||||
trim_whitespace: true,
|
||||
new_line: "unix",
|
||||
indent: "\t"
|
||||
};
|
||||
|
||||
// Prepare Factory-Module-Definition settings
|
||||
_.each(options, function (conf, name) {
|
||||
_.each(options, (conf, name) => {
|
||||
var sources = [],
|
||||
|
||||
opts = {
|
||||
depends: {}
|
||||
},
|
||||
opts = {
|
||||
depends: {}
|
||||
},
|
||||
|
||||
deps = [];
|
||||
deps = [];
|
||||
|
||||
if (conf.exports) {
|
||||
opts.exports = conf.exports;
|
||||
}
|
||||
if (conf.exports) {
|
||||
opts.exports = conf.exports;
|
||||
}
|
||||
|
||||
if (conf.global) {
|
||||
opts.global = conf.global;
|
||||
}
|
||||
if (conf.global) {
|
||||
opts.global = conf.global;
|
||||
}
|
||||
|
||||
// Find and add self as source
|
||||
_.each(this.filesSrc, function (source) {
|
||||
if (grunt.file.exists(source + name + ".js")) {
|
||||
sources.push(source + name + ".js");
|
||||
}
|
||||
}, this);
|
||||
_.each(this.filesSrc, (source) => {
|
||||
if (grunt.file.exists(source + name + ".js")) {
|
||||
sources.push(source + name + ".js");
|
||||
}
|
||||
});
|
||||
|
||||
if (conf.pack) {
|
||||
// Collect all components
|
||||
deps = _.chain(conf.components)
|
||||
.map(function (depName) {
|
||||
return options[depName].components;
|
||||
})
|
||||
.flatten()
|
||||
.unique()
|
||||
.without(name)
|
||||
.sort(function (a, b) {
|
||||
return options[a].components.indexOf(b) === -1 ? -1 : 1;
|
||||
})
|
||||
.value();
|
||||
// Collect all components
|
||||
deps = _.chain(conf.components)
|
||||
.map(depName => options[depName].components)
|
||||
.flatten()
|
||||
.uniq()
|
||||
.without(name)
|
||||
.sort((a, b) => {
|
||||
return options[a].components.indexOf(b) === -1 ? -1 : 1;
|
||||
})
|
||||
.value();
|
||||
|
||||
// Add components as source files -> results a single file
|
||||
_.each(this.filesSrc, function (source) {
|
||||
_.each(deps, function (depName) {
|
||||
if (grunt.file.exists(source + depName + ".js")) {
|
||||
sources.push(source + depName + ".js");
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
_.each(this.filesSrc, (source) => {
|
||||
_.each(deps, (depName) => {
|
||||
if (grunt.file.exists(source + depName + ".js")) {
|
||||
sources.push(source + depName + ".js");
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Read components and add them as dependecies
|
||||
_.each(_.without(conf.components, name), function (value, i) {
|
||||
opts.depends['./' + value] = value === "core" ? "CryptoJS" : null;
|
||||
});
|
||||
}
|
||||
// Read components and add them as dependecies
|
||||
_.each(_.without(conf.components, name), (value, i) => {
|
||||
opts.depends['./' + value] = value === "core" ? "CryptoJS" : null;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
sources = _.unique(sources);
|
||||
// Remove duplicates
|
||||
sources = _.uniq(sources);
|
||||
|
||||
// Add module settings to fmd definition
|
||||
modules[name] = [sources, opts];
|
||||
}, this);
|
||||
modules[name] = [sources, opts];
|
||||
});
|
||||
|
||||
// Build packege modules
|
||||
fmd(config)
|
||||
.define(modules)
|
||||
.build(function (createdFiles) {
|
||||
// Build packege modules
|
||||
fmd(config)
|
||||
.define(modules)
|
||||
.build(() => {
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
+5
-4
@@ -2,7 +2,7 @@
|
||||
"name": "crypto-js",
|
||||
"title": "crypto-js",
|
||||
"description": "JavaScript library of crypto standards.",
|
||||
"version": "3.1.9",
|
||||
"version": "4.0.0",
|
||||
"homepage": "http://github.com/brix/crypto-js",
|
||||
"author": {
|
||||
"name": "Evan Vosberg",
|
||||
@@ -18,20 +18,21 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "grunt build",
|
||||
"check": "grunt default"
|
||||
"test": "grunt default"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"fmd": "~0.0.3",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-cli": "^1.3.2",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-copy": "^0.6.0",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-jshint": "^2.1.0",
|
||||
"grunt-jsonlint": "^1.0.4",
|
||||
"grunt-update-json": "^0.2.0",
|
||||
"load-grunt-config": "^0.16.0",
|
||||
"lodash": "^3.5.0"
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"keywords": [
|
||||
"security",
|
||||
|
||||
+3
-1
@@ -76,6 +76,8 @@
|
||||
*/
|
||||
var AES = C_algo.AES = BlockCipher.extend({
|
||||
_doReset: function () {
|
||||
var t;
|
||||
|
||||
// Skip reset of nRounds has been set before and key did not change
|
||||
if (this._nRounds && this._keyPriorReset === this._key) {
|
||||
return;
|
||||
@@ -98,7 +100,7 @@
|
||||
if (ksRow < keySize) {
|
||||
keySchedule[ksRow] = keyWords[ksRow];
|
||||
} else {
|
||||
var t = keySchedule[ksRow - 1];
|
||||
t = keySchedule[ksRow - 1];
|
||||
|
||||
if (!(ksRow % keySize)) {
|
||||
// Rot word
|
||||
|
||||
+19
-9
@@ -336,17 +336,19 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
});
|
||||
|
||||
function xorBlock(words, offset, blockSize) {
|
||||
var block;
|
||||
|
||||
// Shortcut
|
||||
var iv = this._iv;
|
||||
|
||||
// Choose mixing block
|
||||
if (iv) {
|
||||
var block = iv;
|
||||
block = iv;
|
||||
|
||||
// Remove IV for subsequent blocks
|
||||
this._iv = undefined;
|
||||
} else {
|
||||
var block = this._prevBlock;
|
||||
block = this._prevBlock;
|
||||
}
|
||||
|
||||
// XOR blocks
|
||||
@@ -438,6 +440,8 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
}),
|
||||
|
||||
reset: function () {
|
||||
var modeCreator;
|
||||
|
||||
// Reset cipher
|
||||
Cipher.reset.call(this);
|
||||
|
||||
@@ -448,9 +452,9 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
|
||||
// Reset block mode
|
||||
if (this._xformMode == this._ENC_XFORM_MODE) {
|
||||
var modeCreator = mode.createEncryptor;
|
||||
modeCreator = mode.createEncryptor;
|
||||
} else /* if (this._xformMode == this._DEC_XFORM_MODE) */ {
|
||||
var modeCreator = mode.createDecryptor;
|
||||
modeCreator = mode.createDecryptor;
|
||||
// Keep at least one block in the buffer for unpadding
|
||||
this._minBufferSize = 1;
|
||||
}
|
||||
@@ -468,6 +472,8 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
},
|
||||
|
||||
_doFinalize: function () {
|
||||
var finalProcessedBlocks;
|
||||
|
||||
// Shortcut
|
||||
var padding = this.cfg.padding;
|
||||
|
||||
@@ -477,10 +483,10 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
padding.pad(this._data, this.blockSize);
|
||||
|
||||
// Process final blocks
|
||||
var finalProcessedBlocks = this._process(!!'flush');
|
||||
finalProcessedBlocks = this._process(!!'flush');
|
||||
} else /* if (this._xformMode == this._DEC_XFORM_MODE) */ {
|
||||
// Process final blocks
|
||||
var finalProcessedBlocks = this._process(!!'flush');
|
||||
finalProcessedBlocks = this._process(!!'flush');
|
||||
|
||||
// Unpad data
|
||||
padding.unpad(finalProcessedBlocks);
|
||||
@@ -572,15 +578,17 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
* var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams);
|
||||
*/
|
||||
stringify: function (cipherParams) {
|
||||
var wordArray;
|
||||
|
||||
// Shortcuts
|
||||
var ciphertext = cipherParams.ciphertext;
|
||||
var salt = cipherParams.salt;
|
||||
|
||||
// Format
|
||||
if (salt) {
|
||||
var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);
|
||||
wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);
|
||||
} else {
|
||||
var wordArray = ciphertext;
|
||||
wordArray = ciphertext;
|
||||
}
|
||||
|
||||
return wordArray.toString(Base64);
|
||||
@@ -600,6 +608,8 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
* var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString);
|
||||
*/
|
||||
parse: function (openSSLStr) {
|
||||
var salt;
|
||||
|
||||
// Parse base64
|
||||
var ciphertext = Base64.parse(openSSLStr);
|
||||
|
||||
@@ -609,7 +619,7 @@ CryptoJS.lib.Cipher || (function (undefined) {
|
||||
// Test for salt
|
||||
if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) {
|
||||
// Extract salt
|
||||
var salt = WordArray.create(ciphertextWords.slice(2, 4));
|
||||
salt = WordArray.create(ciphertextWords.slice(2, 4));
|
||||
|
||||
// Remove salt from ciphertext
|
||||
ciphertextWords.splice(0, 4);
|
||||
|
||||
+60
-23
@@ -1,12 +1,65 @@
|
||||
/*globals window, global, require*/
|
||||
|
||||
/**
|
||||
* CryptoJS core components.
|
||||
*/
|
||||
var CryptoJS = CryptoJS || (function (Math, undefined) {
|
||||
|
||||
var crypto;
|
||||
|
||||
// Native crypto from window (Browser)
|
||||
if (typeof window !== 'undefined' && window.crypto) {
|
||||
crypto = window.crypto;
|
||||
}
|
||||
|
||||
// Native (experimental IE 11) crypto from window (Browser)
|
||||
if (!crypto && typeof window !== 'undefined' && window.msCrypto) {
|
||||
crypto = window.msCrypto;
|
||||
}
|
||||
|
||||
// Native crypto from global (NodeJS)
|
||||
if (!crypto && typeof global !== 'undefined' && global.crypto) {
|
||||
crypto = global.crypto;
|
||||
}
|
||||
|
||||
// Native crypto import via require (NodeJS)
|
||||
if (!crypto && typeof require === 'function') {
|
||||
try {
|
||||
crypto = require('crypto');
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local polyfil of Object.create
|
||||
* Cryptographically secure pseudorandom number generator
|
||||
*
|
||||
* As Math.random() is cryptographically not safe to use
|
||||
*/
|
||||
var cryptoSecureRandomInt = function () {
|
||||
if (crypto) {
|
||||
// Use getRandomValues method (Browser)
|
||||
if (typeof crypto.getRandomValues === 'function') {
|
||||
try {
|
||||
return crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// Use randomBytes method (NodeJS)
|
||||
if (typeof crypto.randomBytes === 'function') {
|
||||
try {
|
||||
return crypto.randomBytes(4).readInt32LE();
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Native crypto module could not be used to get secure random number.');
|
||||
};
|
||||
|
||||
/*
|
||||
* Local polyfill of Object.create
|
||||
|
||||
*/
|
||||
var create = Object.create || (function () {
|
||||
function F() {};
|
||||
function F() {}
|
||||
|
||||
return function (obj) {
|
||||
var subtype;
|
||||
@@ -289,26 +342,8 @@ var CryptoJS = CryptoJS || (function (Math, undefined) {
|
||||
random: function (nBytes) {
|
||||
var words = [];
|
||||
|
||||
var r = (function (m_w) {
|
||||
var m_w = m_w;
|
||||
var m_z = 0x3ade68b1;
|
||||
var mask = 0xffffffff;
|
||||
|
||||
return function () {
|
||||
m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
|
||||
m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
|
||||
var result = ((m_z << 0x10) + m_w) & mask;
|
||||
result /= 0x100000000;
|
||||
result += 0.5;
|
||||
return result * (Math.random() > .5 ? 1 : -1);
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0, rcache; i < nBytes; i += 4) {
|
||||
var _r = r((rcache || Math.random()) * 0x100000000);
|
||||
|
||||
rcache = _r() * 0x3ade67b7;
|
||||
words.push((_r() * 0x100000000) | 0);
|
||||
for (var i = 0; i < nBytes; i += 4) {
|
||||
words.push(cryptoSecureRandomInt());
|
||||
}
|
||||
|
||||
return new WordArray.init(words, nBytes);
|
||||
@@ -539,6 +574,8 @@ var CryptoJS = CryptoJS || (function (Math, undefined) {
|
||||
* var processedData = bufferedBlockAlgorithm._process(!!'flush');
|
||||
*/
|
||||
_process: function (doFlush) {
|
||||
var processedWords;
|
||||
|
||||
// Shortcuts
|
||||
var data = this._data;
|
||||
var dataWords = data.words;
|
||||
@@ -571,7 +608,7 @@ var CryptoJS = CryptoJS || (function (Math, undefined) {
|
||||
}
|
||||
|
||||
// Remove processed words
|
||||
var processedWords = dataWords.splice(0, nWordsReady);
|
||||
processedWords = dataWords.splice(0, nWordsReady);
|
||||
data.sigBytes -= nBytesReady;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -106,7 +106,8 @@
|
||||
if (i % 4) {
|
||||
var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2);
|
||||
var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2);
|
||||
words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);
|
||||
var bitsCombined = bits1 | bits2;
|
||||
words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8);
|
||||
nBytes++;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -53,6 +53,8 @@
|
||||
* var key = kdf.compute(password, salt);
|
||||
*/
|
||||
compute: function (password, salt) {
|
||||
var block;
|
||||
|
||||
// Shortcut
|
||||
var cfg = this.cfg;
|
||||
|
||||
@@ -72,7 +74,7 @@
|
||||
if (block) {
|
||||
hasher.update(block);
|
||||
}
|
||||
var block = hasher.update(password).finalize(salt);
|
||||
block = hasher.update(password).finalize(salt);
|
||||
hasher.reset();
|
||||
|
||||
// Iterations
|
||||
|
||||
+4
-2
@@ -34,17 +34,19 @@ CryptoJS.mode.CFB = (function () {
|
||||
});
|
||||
|
||||
function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) {
|
||||
var keystream;
|
||||
|
||||
// Shortcut
|
||||
var iv = this._iv;
|
||||
|
||||
// Generate keystream
|
||||
if (iv) {
|
||||
var keystream = iv.slice(0);
|
||||
keystream = iv.slice(0);
|
||||
|
||||
// Remove IV for subsequent blocks
|
||||
this._iv = undefined;
|
||||
} else {
|
||||
var keystream = this._prevBlock;
|
||||
keystream = this._prevBlock;
|
||||
}
|
||||
cipher.encryptBlock(keystream, 0);
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ CryptoJS.pad.ZeroPadding = {
|
||||
|
||||
// Unpad
|
||||
var i = data.sigBytes - 1;
|
||||
while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) {
|
||||
i--;
|
||||
for (var i = data.sigBytes - 1; i >= 0; i--) {
|
||||
if (((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) {
|
||||
data.sigBytes = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data.sigBytes = i + 1;
|
||||
}
|
||||
};
|
||||
|
||||
+8
-5
@@ -158,6 +158,9 @@
|
||||
|
||||
// Rho Pi
|
||||
for (var laneIndex = 1; laneIndex < 25; laneIndex++) {
|
||||
var tMsw;
|
||||
var tLsw;
|
||||
|
||||
// Shortcuts
|
||||
var lane = state[laneIndex];
|
||||
var laneMsw = lane.high;
|
||||
@@ -166,11 +169,11 @@
|
||||
|
||||
// Rotate lanes
|
||||
if (rhoOffset < 32) {
|
||||
var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset));
|
||||
var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset));
|
||||
tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset));
|
||||
tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset));
|
||||
} else /* if (rhoOffset >= 32) */ {
|
||||
var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset));
|
||||
var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset));
|
||||
tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset));
|
||||
tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset));
|
||||
}
|
||||
|
||||
// Transpose lanes
|
||||
@@ -205,7 +208,7 @@
|
||||
var lane = state[0];
|
||||
var roundConstant = ROUND_CONSTANTS[round];
|
||||
lane.high ^= roundConstant.high;
|
||||
lane.low ^= roundConstant.low;;
|
||||
lane.low ^= roundConstant.low;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+11
-8
@@ -127,13 +127,16 @@
|
||||
|
||||
// Rounds
|
||||
for (var i = 0; i < 80; i++) {
|
||||
var Wil;
|
||||
var Wih;
|
||||
|
||||
// Shortcut
|
||||
var Wi = W[i];
|
||||
|
||||
// Extend message
|
||||
if (i < 16) {
|
||||
var Wih = Wi.high = M[offset + i * 2] | 0;
|
||||
var Wil = Wi.low = M[offset + i * 2 + 1] | 0;
|
||||
Wih = Wi.high = M[offset + i * 2] | 0;
|
||||
Wil = Wi.low = M[offset + i * 2 + 1] | 0;
|
||||
} else {
|
||||
// Gamma0
|
||||
var gamma0x = W[i - 15];
|
||||
@@ -158,12 +161,12 @@
|
||||
var Wi16h = Wi16.high;
|
||||
var Wi16l = Wi16.low;
|
||||
|
||||
var Wil = gamma0l + Wi7l;
|
||||
var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0);
|
||||
var Wil = Wil + gamma1l;
|
||||
var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0);
|
||||
var Wil = Wil + Wi16l;
|
||||
var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0);
|
||||
Wil = gamma0l + Wi7l;
|
||||
Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0);
|
||||
Wil = Wil + gamma1l;
|
||||
Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0);
|
||||
Wil = Wil + Wi16l;
|
||||
Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0);
|
||||
|
||||
Wi.high = Wih;
|
||||
Wi.low = Wil;
|
||||
|
||||
+12
-3
@@ -712,11 +712,20 @@
|
||||
// Shortcuts
|
||||
var key = this._key;
|
||||
var keyWords = key.words;
|
||||
// Make sure the key length is valid (64, 128 or >= 192 bit)
|
||||
if (keyWords.length !== 2 && keyWords.length !== 4 && keyWords.length < 6) {
|
||||
throw new Error('Invalid key length - 3DES requires the key length to be 64, 128, 192 or >192.');
|
||||
}
|
||||
|
||||
// Extend the key according to the keying options defined in 3DES standard
|
||||
var key1 = keyWords.slice(0, 2);
|
||||
var key2 = keyWords.length < 4 ? keyWords.slice(0, 2) : keyWords.slice(2, 4);
|
||||
var key3 = keyWords.length < 6 ? keyWords.slice(0, 2) : keyWords.slice(4, 6);
|
||||
|
||||
// Create DES instances
|
||||
this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2)));
|
||||
this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4)));
|
||||
this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6)));
|
||||
this._des1 = DES.createEncryptor(WordArray.create(key1));
|
||||
this._des2 = DES.createEncryptor(WordArray.create(key2));
|
||||
this._des3 = DES.createEncryptor(WordArray.create(key3));
|
||||
},
|
||||
|
||||
encryptBlock: function (M, offset) {
|
||||
|
||||
@@ -17,11 +17,11 @@ YUI.add('algo-hmac-md5-test', function (Y) {
|
||||
},
|
||||
|
||||
testVector4: function () {
|
||||
Y.Assert.areEqual('7ee2a3cc979ab19865704644ce13355c', C.HmacMD5('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A'));
|
||||
Y.Assert.areEqual('7ee2a3cc979ab19865704644ce13355c', C.HmacMD5('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString());
|
||||
},
|
||||
|
||||
testVector5: function () {
|
||||
Y.Assert.areEqual('0e1bd89c43e3e6e3b3f8cf1d5ba4f77a', C.HmacMD5('abcdefghijklmnopqrstuvwxyz', 'A'));
|
||||
Y.Assert.areEqual('0e1bd89c43e3e6e3b3f8cf1d5ba4f77a', C.HmacMD5('abcdefghijklmnopqrstuvwxyz', 'A').toString());
|
||||
},
|
||||
|
||||
testUpdate: function () {
|
||||
|
||||
@@ -17,11 +17,11 @@ YUI.add('algo-hmac-sha224-test', function (Y) {
|
||||
},
|
||||
|
||||
testVector4: function () {
|
||||
Y.Assert.areEqual('61bf669da4fdcd8e5c3bd09ebbb4a986d3d1b298d3ca05c511f7aeff', C.HmacSHA224('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A'));
|
||||
Y.Assert.areEqual('61bf669da4fdcd8e5c3bd09ebbb4a986d3d1b298d3ca05c511f7aeff', C.HmacSHA224('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString());
|
||||
},
|
||||
|
||||
testVector5: function () {
|
||||
Y.Assert.areEqual('16fc69ada3c3edc1fe9144d6b98d93393833ae442bedf681110a1176', C.HmacSHA224('abcdefghijklmnopqrstuvwxyz', 'A'));
|
||||
Y.Assert.areEqual('16fc69ada3c3edc1fe9144d6b98d93393833ae442bedf681110a1176', C.HmacSHA224('abcdefghijklmnopqrstuvwxyz', 'A').toString());
|
||||
},
|
||||
|
||||
testUpdate: function () {
|
||||
|
||||
@@ -17,11 +17,11 @@ YUI.add('algo-hmac-sha256-test', function (Y) {
|
||||
},
|
||||
|
||||
testVector4: function () {
|
||||
Y.Assert.areEqual('a89dc8178c1184a62df87adaa77bf86e93064863d93c5131140b0ae98b866687', C.HmacSHA256('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A'));
|
||||
Y.Assert.areEqual('a89dc8178c1184a62df87adaa77bf86e93064863d93c5131140b0ae98b866687', C.HmacSHA256('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString());
|
||||
},
|
||||
|
||||
testVector5: function () {
|
||||
Y.Assert.areEqual('d8cb78419c02fe20b90f8b77427dd9f81817a751d74c2e484e0ac5fc4e6ca986', C.HmacSHA256('abcdefghijklmnopqrstuvwxyz', 'A'));
|
||||
Y.Assert.areEqual('d8cb78419c02fe20b90f8b77427dd9f81817a751d74c2e484e0ac5fc4e6ca986', C.HmacSHA256('abcdefghijklmnopqrstuvwxyz', 'A').toString());
|
||||
},
|
||||
|
||||
testUpdate: function () {
|
||||
|
||||
@@ -17,11 +17,11 @@ YUI.add('algo-hmac-sha384-test', function (Y) {
|
||||
},
|
||||
|
||||
testVector4: function () {
|
||||
Y.Assert.areEqual('365dfb271adb8e30fe6c74220b75df1b38c2d19b9d37f2e5a0ec2f3f22bd0406bf5b786e98d81b82c36d3d8a1be6cd07', C.HmacSHA384('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A'));
|
||||
Y.Assert.areEqual('365dfb271adb8e30fe6c74220b75df1b38c2d19b9d37f2e5a0ec2f3f22bd0406bf5b786e98d81b82c36d3d8a1be6cd07', C.HmacSHA384('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString());
|
||||
},
|
||||
|
||||
testVector5: function () {
|
||||
Y.Assert.areEqual('a8357d5e84da64140e41545562ae0782e2a58e39c6cd98939fad8d9080e774c84b7eaca4ba07f6dbf0f12eab912c5285', C.HmacSHA384('abcdefghijklmnopqrstuvwxyz', 'A'));
|
||||
Y.Assert.areEqual('a8357d5e84da64140e41545562ae0782e2a58e39c6cd98939fad8d9080e774c84b7eaca4ba07f6dbf0f12eab912c5285', C.HmacSHA384('abcdefghijklmnopqrstuvwxyz', 'A').toString());
|
||||
},
|
||||
|
||||
testUpdate: function () {
|
||||
|
||||
@@ -17,11 +17,11 @@ YUI.add('algo-hmac-sha512-test', function (Y) {
|
||||
},
|
||||
|
||||
testVector4: function () {
|
||||
Y.Assert.areEqual('a303979f7c94bb39a8ab6ce05cdbe28f0255da8bb305263e3478ef7e855f0242729bf1d2be55398f14da8e63f0302465a8a3f76c297bd584ad028d18ed7f0195', C.HmacSHA512('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A'));
|
||||
Y.Assert.areEqual('a303979f7c94bb39a8ab6ce05cdbe28f0255da8bb305263e3478ef7e855f0242729bf1d2be55398f14da8e63f0302465a8a3f76c297bd584ad028d18ed7f0195', C.HmacSHA512('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString());
|
||||
},
|
||||
|
||||
testVector5: function () {
|
||||
Y.Assert.areEqual('8c2d56f7628325e62124c0a870ad98d101327fc42696899a06ce0d7121454022fae597e42c25ac3a4c380fd514f553702a5b0afaa9b5a22050902f024368e9d9', C.HmacSHA512('abcdefghijklmnopqrstuvwxyz', 'A'));
|
||||
Y.Assert.areEqual('8c2d56f7628325e62124c0a870ad98d101327fc42696899a06ce0d7121454022fae597e42c25ac3a4c380fd514f553702a5b0afaa9b5a22050902f024368e9d9', C.HmacSHA512('abcdefghijklmnopqrstuvwxyz', 'A').toString());
|
||||
},
|
||||
|
||||
testUpdate: function () {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CryptoJS Test Suite</title>
|
||||
|
||||
<!-- CryptoJS -->
|
||||
<script src="../build/crypto-js.js"></script>
|
||||
|
||||
<!-- YUI -->
|
||||
<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
|
||||
|
||||
<!-- Tests -->
|
||||
<script src="lib-base-test.js"></script>
|
||||
<script src="lib-wordarray-test.js"></script>
|
||||
<script src="lib-typedarrays-test.js"></script>
|
||||
<script src="x64-word-test.js"></script>
|
||||
<script src="x64-wordarray-test.js"></script>
|
||||
<script src="enc-hex-test.js"></script>
|
||||
<script src="enc-latin1-test.js"></script>
|
||||
<script src="enc-utf8-test.js"></script>
|
||||
<script src="enc-utf16-test.js"></script>
|
||||
<script src="enc-base64-test.js"></script>
|
||||
<script src="md5-test.js"></script>
|
||||
<script src="sha1-test.js"></script>
|
||||
<script src="sha256-test.js"></script>
|
||||
<script src="sha224-test.js"></script>
|
||||
<script src="sha512-test.js"></script>
|
||||
<script src="sha384-test.js"></script>
|
||||
<script src="sha3-test.js"></script>
|
||||
<script src="ripemd160-test.js"></script>
|
||||
<script src="hmac-md5-test.js"></script>
|
||||
<script src="hmac-sha224-test.js"></script>
|
||||
<script src="hmac-sha256-test.js"></script>
|
||||
<script src="hmac-sha384-test.js"></script>
|
||||
<script src="hmac-sha512-test.js"></script>
|
||||
<script src="pbkdf2-test.js"></script>
|
||||
<script src="evpkdf-test.js"></script>
|
||||
<script src="mode-cbc-test.js"></script>
|
||||
<script src="mode-cfb-test.js"></script>
|
||||
<script src="mode-ctr-test.js"></script>
|
||||
<script src="mode-ofb-test.js"></script>
|
||||
<script src="mode-ecb-test.js"></script>
|
||||
<script src="pad-pkcs7-test.js"></script>
|
||||
<script src="pad-ansix923-test.js"></script>
|
||||
<script src="pad-iso10126-test.js"></script>
|
||||
<script src="pad-zeropadding-test.js"></script>
|
||||
<script src="pad-iso97971-test.js"></script>
|
||||
<script src="lib-cipherparams-test.js"></script>
|
||||
<script src="format-openssl-test.js"></script>
|
||||
<script src="lib-serializablecipher-test.js"></script>
|
||||
<script src="kdf-openssl-test.js"></script>
|
||||
<script src="lib-passwordbasedcipher-test.js"></script>
|
||||
<script src="rc4-test.js"></script>
|
||||
<script src="rabbit-test.js"></script>
|
||||
<script src="rabbit-legacy-test.js"></script>
|
||||
<script src="cipher-test.js"></script>
|
||||
<script src="aes-test.js"></script>
|
||||
<script src="des-test.js"></script>
|
||||
<script src="tripledes-test.js"></script>
|
||||
|
||||
<!-- Test runner -->
|
||||
<script>
|
||||
YUI().use('test', 'console', function (Y) {
|
||||
Y.use('*', function (Y) {
|
||||
new Y.Console({
|
||||
style: 'block',
|
||||
width: '600px',
|
||||
height: '600px',
|
||||
entryTemplate:
|
||||
'<div class="{entry_class} {cat_class} {src_class}">' +
|
||||
' <p class="{entry_meta_class}">' +
|
||||
' <span class="{entry_cat_class}">{category}</span>' +
|
||||
' </p>' +
|
||||
' <pre class="{entry_content_class}">{message}</pre>' +
|
||||
'</div>',
|
||||
newestOnTop: false,
|
||||
consoleLimit: 500,
|
||||
render: true
|
||||
});
|
||||
|
||||
Y.Test.Runner.run();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body.yui3-skin-sam .yui3-console-entry-meta,
|
||||
body.yui3-skin-sam .yui3-console-entry-content {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
body.yui3-skin-sam .yui3-console-entry-cat {
|
||||
display: inline-block;
|
||||
width: 35px;
|
||||
text-align: center;
|
||||
}
|
||||
body.yui3-skin-sam .yui3-console-entry {
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="yui3-skin-sam">
|
||||
</body>
|
||||
</html>
|
||||
@@ -62,6 +62,39 @@ YUI.add('algo-tripledes-test', function (Y) {
|
||||
Y.Assert.areEqual(expectedIv, iv.toString());
|
||||
},
|
||||
|
||||
test64BitKey: function() {
|
||||
var message = C.enc.Hex.parse('00112233445566778899aabbccddeeff');
|
||||
var key = C.enc.Hex.parse('0011223344556677');
|
||||
var extendedKey = C.enc.Hex.parse('001122334455667700112233445566770011223344556677')
|
||||
|
||||
var output1 = C.TripleDES.encrypt(message, key, { mode: C.mode.ECB }).toString();
|
||||
var output2 = C.TripleDES.encrypt(message, extendedKey, { mode: C.mode.ECB }).toString();
|
||||
|
||||
Y.Assert.areEqual(output1, output2);
|
||||
},
|
||||
|
||||
test128BitKey: function() {
|
||||
var message = C.enc.Hex.parse('00112233445566778899aabbccddeeff');
|
||||
var key = C.enc.Hex.parse('00112233445566778899aabbccddeeff');
|
||||
var extendedKey = C.enc.Hex.parse('00112233445566778899aabbccddeeff0011223344556677')
|
||||
|
||||
var output1 = C.TripleDES.encrypt(message, key, { mode: C.mode.ECB }).toString();
|
||||
var output2 = C.TripleDES.encrypt(message, extendedKey, { mode: C.mode.ECB }).toString();
|
||||
|
||||
Y.Assert.areEqual(output1, output2);
|
||||
},
|
||||
|
||||
test256BitKey: function() {
|
||||
var message = C.enc.Hex.parse('00112233445566778899aabbccddeeff');
|
||||
var key = C.enc.Hex.parse('00112233445566778899aabbccddeeff0112233445566778899aabbccddeeff0');
|
||||
var truncatedKey = C.enc.Hex.parse('00112233445566778899aabbccddeeff0112233445566778')
|
||||
|
||||
var output1 = C.TripleDES.encrypt(message, key, { mode: C.mode.ECB }).toString();
|
||||
var output2 = C.TripleDES.encrypt(message, truncatedKey, { mode: C.mode.ECB }).toString();
|
||||
|
||||
Y.Assert.areEqual(output1, output2);
|
||||
},
|
||||
|
||||
testHelper: function () {
|
||||
// Save original random method
|
||||
var random = C.lib.WordArray.random;
|
||||
|
||||
Reference in New Issue
Block a user