diff --git a/package-lock.json b/package-lock.json index b61d279d87..ac1be03fe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@babel/cli": "7.20.7", "@babel/preset-env": "7.20.2", "@types/jest": "29.4.0", - "eslint": "8.33.0", + "eslint": "^8.33.0", "eslint-config-airbnb": "19.0.4", "eslint-plugin-import": "2.27.5", "eslint-plugin-jest": "27.2.1", @@ -4069,7 +4069,9 @@ "version": "8.33.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint/eslintrc": "^1.4.1", "@humanwhocodes/config-array": "^0.11.8", diff --git a/package.json b/package.json index 95d6d28942..54c909f8e9 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@babel/cli": "7.20.7", "@babel/preset-env": "7.20.2", "@types/jest": "29.4.0", - "eslint": "8.33.0", + "eslint": "^8.33.0", "eslint-config-airbnb": "19.0.4", "eslint-plugin-import": "2.27.5", "eslint-plugin-jest": "27.2.1", diff --git a/src/algorithms/cryptography/sha1/README.md b/src/algorithms/cryptography/sha1/README.md new file mode 100644 index 0000000000..b92ac88c7b --- /dev/null +++ b/src/algorithms/cryptography/sha1/README.md @@ -0,0 +1,55 @@ +# SHA-1 Algorithm + +The SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function that produces a 160-bit (20-byte) hash value, which is often represented as a 40-digit hexadecimal number. It was developed by the National Security Agency (NSA) and published by the National Institute of Standards and Technology (NIST) in 1993 as a part of the Digital Signature Algorithm. + +## How SHA-1 Works +SHA-1 takes an input message of any length and generates a fixed-size 160-bit (20-byte) hash value. The process involves several steps: + +1. **Preprocessing** + - **Padding:** The original message is padded with a '1' bit followed by enough '0' bits to make the message length congruent to 448 modulo 512. This ensures the length of the message is a multiple of 512 bits (64 bytes). + - **Length Append:** The original length of the message (before padding) is represented as a 64-bit binary number and appended to the end of the padded message. + +2. **Initialize Buffers** + - Five 32-bit buffers are used, denoted as `H0`, `H1`, `H2`, `H3`, and `H4`, which are initialized to specific constants derived from the square roots of prime numbers: + - `H0 = 0x67452301` + - `H1 = 0xEFCDAB89` + - `H2 = 0x98BADCFE` + - `H3 = 0x10325476` + - `H4 = 0xC3D2E1F0` + +3. **Processing the Message in 512-bit Chunks** + - The padded message is divided into blocks of 512 bits (64 bytes). Each block is processed through 80 rounds, utilizing bitwise operations, modular addition, and logical functions. + - **Message Schedule Creation:** The 512-bit block is divided into 16 words of 32 bits each, which are then expanded into an 80-word schedule using bitwise operations. + - **Main Loop:** During each round, a different logical function is applied, and the five buffers are updated using the message schedule. This process involves mixing the buffers in a complex way to produce the hash. + +4. **Updating the Buffers** + - After processing each block, the intermediate hash values are added to the existing buffer values. This process ensures that each block of the message contributes to the final hash. + +5. **Producing the Final Hash** + - Once all blocks have been processed, the final hash value is generated by concatenating the five buffers (`H0`, `H1`, `H2`, `H3`, `H4`). This results in a 160-bit (20-byte) output, typically represented as a 40-digit hexadecimal number. + +## Applications of SHA-1 +SHA-1 is widely used in various applications where data integrity verification is essential: +- **Digital Signatures:** It is used in digital signature algorithms to verify the authenticity and integrity of digital documents. +- **Secure Communication Protocols:** SHA-1 is integrated into protocols such as TLS/SSL for ensuring secure communication over networks. +- **Checksum and Data Integrity Verification:** It can be used to detect alterations or corruption in data files. +- **Version Control Systems:** Systems like Git use SHA-1 for identifying commits uniquely. + +## How to Implement SHA-1 +Implementing SHA-1 involves following the step-by-step process outlined above. Libraries in various programming languages (JavaScript, Python, C++, etc.) provide built-in support for computing SHA-1 hashes, making it easier to integrate into projects. + +For example, in JavaScript, a basic implementation could involve creating functions for padding the input, initializing buffers, processing blocks, and producing the final hash. Using these functions, the SHA-1 algorithm can compute the hash of a given input string. + +## How to Use This Code + +- First, import the `sha1` from the directory where it is exported as a constant. + +- The `sha1` object contains two methods: + - `hash`: Takes a string as input and returns its corresponding SHA-1 hash value. + - `compare`: Takes a string and a SHA-1 hash string, and returns `true` if the hash value of the input string matches the given SHA-1 hash; otherwise, it returns `false`. + +## References +- [Wikipedia - SHA-1](https://en.wikipedia.org/wiki/SHA-1) +- [RFC 3174 - US Secure Hash Algorithm 1 (SHA1)](https://tools.ietf.org/html/rfc3174) +- [NIST FIPS PUB 180-4 - Secure Hash Standard (SHS)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) +- [Digital Signature Algorithm Overview](https://csrc.nist.gov/publications/detail/fips/186/4/final) diff --git a/src/algorithms/cryptography/sha1/_test_/sha_1.test.js b/src/algorithms/cryptography/sha1/_test_/sha_1.test.js new file mode 100644 index 0000000000..af62d0cdca --- /dev/null +++ b/src/algorithms/cryptography/sha1/_test_/sha_1.test.js @@ -0,0 +1,56 @@ +import sha1 from '../sha_1'; + +describe('sha1', () => { + it('should return the correct SHA-1 hash for a given input string', () => { + const input = 'hello world'; + const expectedHash = '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'; + expect(sha1.hash(input)).toBe(expectedHash); + }); + + it('should return the correct SHA-1 hash for an empty string', () => { + const input = ''; + const expectedHash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'; + expect(sha1.hash(input)).toBe(expectedHash); + }); + + it('should return the same hash for the same input string', () => { + const input = 'consistent hash'; + const firstHash = sha1.hash(input); + const secondHash = sha1.hash(input); + expect(firstHash).toBe(secondHash); + }); + + it('should return different hashes for different input strings', () => { + const input1 = 'input one'; + const input2 = 'input two'; + const hash1 = sha1.hash(input1); + const hash2 = sha1.hash(input2); + expect(hash1).not.toBe(hash2); + }); + + it('should handle long input strings correctly', () => { + const input = 'a'.repeat(1000); // A string of 1000 'a' characters + const expectedHash = '291e9a6c66994949b57ba5e650361e98fc36b1ba'; + expect(sha1.hash(input)).toBe(expectedHash); + }); + + it('should handle long complex input strings correctly', () => { + const input = 'the best $-percentage is below x% & ^ this one'; + const expectedHash = '90c7605658a6e7bec952c3b818285e32b79ac304'; + expect(sha1.hash(input)).toBe(expectedHash); + }); + + it('should return true while comparing the string with its corresponding hash value', () => { + const input = 'the best $-percentage is below x% & ^ this one'; + const inputHash = '90c7605658a6e7bec952c3b818285e32b79ac304'; + const expectedOutput = true; + expect(sha1.compare(input, inputHash)).toBe(expectedOutput); + }); + + it('should return false while comparing the string with diffrent hash value', () => { + const input = 'the best $-percentage is below x% & ^ this one'; + const inputHash = '291e9a6c66994949b57ba5e650361e98fc36b1ba'; + const expectedOutput = false; + expect(sha1.compare(input, inputHash)).toBe(expectedOutput); + }); +}); diff --git a/src/algorithms/cryptography/sha1/sha_1.js b/src/algorithms/cryptography/sha1/sha_1.js new file mode 100644 index 0000000000..4e22757e50 --- /dev/null +++ b/src/algorithms/cryptography/sha1/sha_1.js @@ -0,0 +1,370 @@ +/** + * Encodes the input string into the format required by the SHA-1 algorithm, + * preparing it as a series of 512-bit (64-byte) blocks, each consisting of + * sixteen 32-bit words. This preprocessing step includes padding the input + * to the correct length, which is necessary for SHA-1 to process the data + * correctly. + * + * The encoding process includes: + * + * 1. **Breaking the Input into 512-bit Chunks**: + * - The input string is divided into 512-bit (64-byte) chunks. Each chunk + * is represented as an array of sixteen 32-bit words (Uint32Array). + * + * 2. **Converting Characters to 32-bit Words**: + * - The characters of the input string are converted to their character + * codes and then arranged into 32-bit words. The bit-shifting ensures + * the correct positioning within each 32-bit word. + * + * 3. **Padding the Input**: + * - If the total length is not a multiple of 512 bits, the input is padded + * to make it so. Padding consists of a '1' bit followed by '0' bits, + * and the original message length (in bits) is appended as a 64-bit + * integer in the final 512-bit block. + * - The `handlePadding` function manages this padding step: + * - It calculates the appropriate bit to set in the padding. + * - Adds the original message length (in bits) as the last 32-bit word + * if necessary. + * + * 4. **Handling Multiple Blocks**: + * - If the current block becomes full or the message ends, it is added to + * the list of chunks, and a new chunk is started. + * + * @param {string} inputData - The input string to be preprocessed for SHA-1. + * @returns {Array} - An array of 512-bit chunks, each represented + * as a Uint32Array of sixteen 32-bit words. + */ +const getInputDataEncodings = (inputData) => { + const inputDataInChunk = []; + let subChunks = new Uint32Array(16); + let count = 0; + let placeHolder = 0; + let rotateCount = 0; + + const handlePadding = () => { + const rotateIndex = (3 - rotateCount) * 8 + 7; + + const paddingValue = (1 << rotateIndex) >>> 0; + + placeHolder |= paddingValue; + + const index = Math.floor(count / 4); + + subChunks[index] = placeHolder; + + if (index >= 14) { + inputDataInChunk.push(subChunks); + subChunks = new Uint32Array(16); + } + + subChunks[15] = inputData.length * 8; + + inputDataInChunk.push(subChunks); + }; + + for (let i = 0; i < inputData.length; i += 1) { + let c = inputData.charCodeAt(i); + + c = (c << ((3 - rotateCount) * 8)) >>> 0; + + placeHolder = (placeHolder | c) >>> 0; + + count += 1; + + rotateCount = (rotateCount + 1) % 4; + + if (rotateCount === 0) { + subChunks[count / 4 - 1] = placeHolder; + placeHolder = 0; + } + + count %= 64; + + if (count === 0) { + inputDataInChunk.push(subChunks); + subChunks = new Uint32Array(16); + } + } + + handlePadding(); + return inputDataInChunk; +}; + +/** + * Funtion to left rotate a value + * + * @param {Unsigned int32} x - range from 0 to 2^32 - 1 ,this is the number which have to be rotated + * @param {Integer} n -range from 0 to 32 , this represent the number of times + * @return {Integer} - return the value of x after left rotating it by n times + */ +const rotateToLeft = (x, n) => (x << n) | (x >>> (32 - n)); + +/** + * Funtion to conver the final encodings to hexadecimal form + * + * @param {Uint32Array} inputArry - holds the value of the 5 encoding in a 32bit formate + * @return {stringr} - return the string of hexadecimal values + */ +const bitsToString = (inputArry) => { + let outputString = ''; + inputArry.map((data32Bit) => { + outputString += data32Bit.toString(16).padStart(8, '0'); + return 0; + }); + + return outputString; +}; + +/** + * Performs a specific bitwise operation on the inputs b, c, and d, + * depending on the current iteration count (t) in the SHA-1 algorithm. + * + * In the SHA-1 algorithm, the processing function varies for different + * ranges of t (0-79) to ensure non-linearity and complexity in the + * message digest calculation: + * + * - 0 <= t <= 19: Returns the result of the majority function: (b & c) | (~b & d) + * - This emphasizes the bits where b and c match or where b is not set. + * + * - 20 <= t <= 39: Returns the result of the XOR function: b ^ c ^ d + * - This introduces more randomness by performing an XOR of b, c, and d. + * + * - 40 <= t <= 59: Returns the result of the majority function: (b & c) | (b & d) | (c & d) + * - This emphasizes the bits where the majority of b, c, and d match. + * + * - 60 <= t <= 79: Returns the result of the XOR function: b ^ c ^ d + * - The same XOR function is used again to increase mixing in the final stages. + * + * @param {number} b - The first input word for the operation. + * @param {number} c - The second input word for the operation. + * @param {number} d - The third input word for the operation. + * @param {number} count - The current iteration count (t) ranging from 0 to 79. + * @returns {number} - The result of the bitwise operation based on the value of count. + */ +const processingFunction = (b, c, d, count) => { + if (count >= 0 && count <= 19) { + return ((b & c) | (~b & d)) >>> 0; + } + + if (count >= 20 && count <= 39) { + return (b ^ c ^ d) >>> 0; + } + + if (count >= 40 && count <= 59) { + return ((b & c) | (b & d) | (c & d)) >>> 0; + } + + if (count >= 60 && count <= 79) { + return (b ^ c ^ d) >>> 0; + } + + return 0; +}; + +/** + * Returns a specific constant value used in the SHA-1 algorithm, + * depending on the current iteration count (t). + * + * In the SHA-1 algorithm, the processing constants vary across different + * ranges of t (0-79). These constants are used in each round to add + * further non-linearity and mixing into the message digest calculation: + * + * - 0 <= t <= 19: Returns 1518500249 (0x5A827999) + * - This constant is used during the first 20 rounds. + * + * - 20 <= t <= 39: Returns 1859775393 (0x6ED9EBA1) + * - This constant is used during the next 20 rounds. + * + * - 40 <= t <= 59: Returns 2400959708 (0x8F1BBCDC) + * - This constant is used during the middle 20 rounds. + * + * - 60 <= t <= 79: Returns 3395469782 (0xCA62C1D6) + * - This constant is used during the final 20 rounds. + * + * The purpose of these constants is to introduce additional bit-level + * randomness and complexity into the hash function, making it more + * resistant to cryptographic attacks. + * + * @param {number} count - The current iteration count (t) ranging from 0 to 79. + * @returns {number} - The corresponding constant value for the specified range. + */ +const processingConstants = (count) => { + if (count >= 0 && count <= 19) { + return 1518500249; + } + + if (count >= 20 && count <= 39) { + return 1859775393; + } + + if (count >= 40 && count <= 59) { + return 2400959708; + } + + if (count >= 60 && count <= 79) { + return 3395469782; + } + + return 0; +}; + +/** + * Computes the SHA-1 hash for the given input string by processing it through + * the SHA-1 compression function. This function implements the main steps + * of the SHA-1 algorithm, including message scheduling, buffer initialization, + * and iterative rounds of compression. + * + * Steps in the SHA-1 algorithm: + * + * 1. **Preprocessing**: + * - The input string is converted into an array of encoded data blocks. + * Each block is 512 bits (64 bytes) in length, represented as an array of + * 32-bit words (16 words per block). + * + * 2. **Initialize the buffer**: + * - A 160-bit buffer (five 32-bit words) is initialized with specific + * constants. These constants are defined by the SHA-1 standard: + * H0 = 0x67452301, H1 = 0xEFCDAB89, H2 = 0x98BADCFE, + * H3 = 0x10325476, H4 = 0xC3D2E1F0. + * + * 3. **Message Schedule Expansion**: + * - For each 512-bit block, a message schedule of 80 32-bit words is + * created. The first 16 words come from the input block, and the + * remaining 64 words are generated by XORing earlier schedule values + * and rotating them to the left. + * + * 4. **Main Loop (80 rounds)**: + * - For each of the 80 iterations, the buffer values are updated using: + * - A bitwise rotation of the first buffer value. + * - The output of the `processingFunction`, which varies based on the + * iteration count `t` to introduce non-linearity. + * - The message schedule value for the current round. + * - A constant value from `processingConstants`, which also depends on `t`. + * - The buffer is updated in a circular manner, mimicking a simple + * feedback mechanism. + * + * 5. **Update the Hash Values**: + * - After processing each block, the buffer values are added to the + * original hash values (H0, H1, H2, H3, H4), producing the intermediate + * hash values for the next block. + * + * 6. **Final Output**: + * - After all blocks have been processed, the 160-bit buffer (now + * representing H0, H1, H2, H3, and H4) is converted to its final + * hexadecimal string representation, which is the SHA-1 hash. + * + * @param {string} inputString - The input string to be hashed using the SHA-1 algorithm. + * @returns {string} - The SHA-1 hash of the input string, represented as a + * 40-character hexadecimal string. + */ +const compressFunction = (inputString) => { + const inputEncodings = getInputDataEncodings(inputString); + const innitialBuffer = new Uint32Array(5); + + innitialBuffer[0] = 1732584193; + innitialBuffer[1] = 4023233417; + innitialBuffer[2] = 2562383102; + innitialBuffer[3] = 271733878; + innitialBuffer[4] = 3285377520; + + for (let i = 0; i < inputEncodings.length; i += 1) { + const messageScheduleArray = new Uint32Array(80); + + messageScheduleArray.set(inputEncodings[i]); + + const currentBuffer = new Uint32Array(5); + currentBuffer.set(innitialBuffer); + + for (let t = 16; t <= 79; t += 1) { + const currentVariable = messageScheduleArray[t - 3] + ^ messageScheduleArray[t - 8] + ^ messageScheduleArray[t - 14] + ^ messageScheduleArray[t - 16]; + messageScheduleArray[t] = rotateToLeft(currentVariable, 1) >>> 0; + } + + for (let k = 0; k <= 79; k += 1) { + const temp = (rotateToLeft(currentBuffer[0], 5) + + processingFunction(currentBuffer[1], currentBuffer[2], currentBuffer[3], k) + + currentBuffer[4] + messageScheduleArray[k] + processingConstants(k)) & 0xffffffff; + + [currentBuffer[4], currentBuffer[3]] = [ + currentBuffer[3], + currentBuffer[2], + ]; + currentBuffer[2] = rotateToLeft(currentBuffer[1], 30); + [currentBuffer[1]] = [currentBuffer[0]]; + currentBuffer[0] = temp; + } + + innitialBuffer[0] = (innitialBuffer[0] + currentBuffer[0]) & 0xffffffff; + innitialBuffer[1] = (innitialBuffer[1] + currentBuffer[1]) & 0xffffffff; + innitialBuffer[2] = (innitialBuffer[2] + currentBuffer[2]) & 0xffffffff; + innitialBuffer[3] = (innitialBuffer[3] + currentBuffer[3]) & 0xffffffff; + innitialBuffer[4] = (innitialBuffer[4] + currentBuffer[4]) & 0xffffffff; + } + + return bitsToString(innitialBuffer); +}; + +/** + * The `sha1` object provides methods for hashing a string using the SHA-1 + * algorithm and comparing an input string's hash with a given SHA-1 encoding. + * + * This object contains two methods: + * + * 1. **hash(inputString)**: + * - Takes an input string and returns its SHA-1 hash. + * - Performs input validation: + * - Throws an error if the input is not a string. + * - Throws an error if the input is too large to encode + * (exceeding 4,294,967,295 bits). + * - Uses the `compressFunction` to compute the SHA-1 hash for the given input. + * + * @param {string} inputString - The string to be hashed using the SHA-1 algorithm. + * @returns {string} - The 40-character hexadecimal representation of the SHA-1 hash. + * @throws {Error} - Throws an error if the input is not a string or if the + * input size exceeds the allowable length for SHA-1. + * + * 2. **compare(inputString, inputEncoding)**: + * - Compares the SHA-1 hash of the input string with the provided SHA-1 encoding. + * - Performs input validation: + * - Throws an error if either the input string or the input encoding is not a string. + * - Returns `false` if the input string's length in bits exceeds 4,294,967,295 or + * if the input encoding is not a valid 40-character hexadecimal string. + * - Uses the `compressFunction` to compute the SHA-1 hash of the input string + * and checks for equality with the given SHA-1 encoding. + * + * @param {string} inputString - The string whose SHA-1 hash is to be compared. + * @param {string} inputEncoding - The SHA-1 hash string to compare against. + * @returns {boolean} - Returns `true` if the SHA-1 hash of the input string matches + * the given encoding, otherwise returns `false`. + * @throws {Error} - Throws an error if the input parameters are not valid strings. + */ +const sha1 = { + hash: (inputString) => { + if (typeof inputString !== 'string') { + throw new Error('provide a valid string input'); + } + + if (inputString.length * 8 > 4294967295) { + throw new Error('The string is too large to encode'); + } + + return compressFunction(inputString); + }, + + compare: (inputString, inputEncoding) => { + if (typeof inputString !== 'string' || typeof inputEncoding !== 'string') { + throw new Error('provide a valid string inputs'); + } + + if (inputString.length * 8 > 4294967295 || inputEncoding.length > 40) { + return false; + } + + return compressFunction(inputString) === inputEncoding; + }, +}; + +export default sha1;