Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | |
michael@0 | 6 | // A very thin wrapper around nsICryptoHash. It's not strictly |
michael@0 | 7 | // necessary, but makes the code a bit cleaner and gives us the |
michael@0 | 8 | // opportunity to verify that our implementations give the results that |
michael@0 | 9 | // we expect, for example if we have to interoperate with a server. |
michael@0 | 10 | // |
michael@0 | 11 | // The digest* methods reset the state of the hasher, so it's |
michael@0 | 12 | // necessary to call init() explicitly after them. |
michael@0 | 13 | // |
michael@0 | 14 | // Works only in Firefox 1.5+. |
michael@0 | 15 | // |
michael@0 | 16 | // IMPORTANT NOTE: Due to https://bugzilla.mozilla.org/show_bug.cgi?id=321024 |
michael@0 | 17 | // you cannot use the cryptohasher before app-startup. The symptom of doing |
michael@0 | 18 | // so is a segfault in NSS. |
michael@0 | 19 | |
michael@0 | 20 | /** |
michael@0 | 21 | * Instantiate a new hasher. You must explicitly call init() before use! |
michael@0 | 22 | */ |
michael@0 | 23 | function G_CryptoHasher() { |
michael@0 | 24 | this.debugZone = "cryptohasher"; |
michael@0 | 25 | this.hasher_ = null; |
michael@0 | 26 | } |
michael@0 | 27 | |
michael@0 | 28 | G_CryptoHasher.algorithms = { |
michael@0 | 29 | MD2: Ci.nsICryptoHash.MD2, |
michael@0 | 30 | MD5: Ci.nsICryptoHash.MD5, |
michael@0 | 31 | SHA1: Ci.nsICryptoHash.SHA1, |
michael@0 | 32 | SHA256: Ci.nsICryptoHash.SHA256, |
michael@0 | 33 | SHA384: Ci.nsICryptoHash.SHA384, |
michael@0 | 34 | SHA512: Ci.nsICryptoHash.SHA512, |
michael@0 | 35 | }; |
michael@0 | 36 | |
michael@0 | 37 | /** |
michael@0 | 38 | * Initialize the hasher. This function must be called after every call |
michael@0 | 39 | * to one of the digest* methods. |
michael@0 | 40 | * |
michael@0 | 41 | * @param algorithm Constant from G_CryptoHasher.algorithms specifying the |
michael@0 | 42 | * algorithm this hasher will use |
michael@0 | 43 | */ |
michael@0 | 44 | G_CryptoHasher.prototype.init = function(algorithm) { |
michael@0 | 45 | var validAlgorithm = false; |
michael@0 | 46 | for (var alg in G_CryptoHasher.algorithms) |
michael@0 | 47 | if (algorithm == G_CryptoHasher.algorithms[alg]) |
michael@0 | 48 | validAlgorithm = true; |
michael@0 | 49 | |
michael@0 | 50 | if (!validAlgorithm) |
michael@0 | 51 | throw new Error("Invalid algorithm: " + algorithm); |
michael@0 | 52 | |
michael@0 | 53 | this.hasher_ = Cc["@mozilla.org/security/hash;1"] |
michael@0 | 54 | .createInstance(Ci.nsICryptoHash); |
michael@0 | 55 | this.hasher_.init(algorithm); |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | /** |
michael@0 | 59 | * Update the hash's internal state with input given in a string. Can be |
michael@0 | 60 | * called multiple times for incrementeal hash updates. |
michael@0 | 61 | * |
michael@0 | 62 | * @param input String containing data to hash. |
michael@0 | 63 | */ |
michael@0 | 64 | G_CryptoHasher.prototype.updateFromString = function(input) { |
michael@0 | 65 | if (!this.hasher_) |
michael@0 | 66 | throw new Error("You must initialize the hasher first!"); |
michael@0 | 67 | |
michael@0 | 68 | var stream = Cc['@mozilla.org/io/string-input-stream;1'] |
michael@0 | 69 | .createInstance(Ci.nsIStringInputStream); |
michael@0 | 70 | stream.setData(input, input.length); |
michael@0 | 71 | this.updateFromStream(stream); |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | /** |
michael@0 | 75 | * Update the hash's internal state with input given in an array. Can be |
michael@0 | 76 | * called multiple times for incremental hash updates. |
michael@0 | 77 | * |
michael@0 | 78 | * @param input Array containing data to hash. |
michael@0 | 79 | */ |
michael@0 | 80 | G_CryptoHasher.prototype.updateFromArray = function(input) { |
michael@0 | 81 | if (!this.hasher_) |
michael@0 | 82 | throw new Error("You must initialize the hasher first!"); |
michael@0 | 83 | |
michael@0 | 84 | this.hasher_.update(input, input.length); |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | /** |
michael@0 | 88 | * Update the hash's internal state with input given in a stream. Can be |
michael@0 | 89 | * called multiple times from incremental hash updates. |
michael@0 | 90 | */ |
michael@0 | 91 | G_CryptoHasher.prototype.updateFromStream = function(stream) { |
michael@0 | 92 | if (!this.hasher_) |
michael@0 | 93 | throw new Error("You must initialize the hasher first!"); |
michael@0 | 94 | |
michael@0 | 95 | if (stream.available()) |
michael@0 | 96 | this.hasher_.updateFromStream(stream, stream.available()); |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | /** |
michael@0 | 100 | * @returns The hash value as a string (sequence of 8-bit values) |
michael@0 | 101 | */ |
michael@0 | 102 | G_CryptoHasher.prototype.digestRaw = function() { |
michael@0 | 103 | var digest = this.hasher_.finish(false /* not b64 encoded */); |
michael@0 | 104 | this.hasher_ = null; |
michael@0 | 105 | return digest; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | /** |
michael@0 | 109 | * @returns The hash value as a base64-encoded string |
michael@0 | 110 | */ |
michael@0 | 111 | G_CryptoHasher.prototype.digestBase64 = function() { |
michael@0 | 112 | var digest = this.hasher_.finish(true /* b64 encoded */); |
michael@0 | 113 | this.hasher_ = null; |
michael@0 | 114 | return digest; |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | /** |
michael@0 | 118 | * @returns The hash value as a hex-encoded string |
michael@0 | 119 | */ |
michael@0 | 120 | G_CryptoHasher.prototype.digestHex = function() { |
michael@0 | 121 | var raw = this.digestRaw(); |
michael@0 | 122 | return this.toHex_(raw); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | /** |
michael@0 | 126 | * Converts a sequence of values to a hex-encoded string. The input is a |
michael@0 | 127 | * a string, so you can stick 16-bit values in each character. |
michael@0 | 128 | * |
michael@0 | 129 | * @param str String to conver to hex. (Often this is just a sequence of |
michael@0 | 130 | * 16-bit values) |
michael@0 | 131 | * |
michael@0 | 132 | * @returns String containing the hex representation of the input |
michael@0 | 133 | */ |
michael@0 | 134 | G_CryptoHasher.prototype.toHex_ = function(str) { |
michael@0 | 135 | var hexchars = '0123456789ABCDEF'; |
michael@0 | 136 | var hexrep = new Array(str.length * 2); |
michael@0 | 137 | |
michael@0 | 138 | for (var i = 0; i < str.length; ++i) { |
michael@0 | 139 | hexrep[i * 2] = hexchars.charAt((str.charCodeAt(i) >> 4) & 15); |
michael@0 | 140 | hexrep[i * 2 + 1] = hexchars.charAt(str.charCodeAt(i) & 15); |
michael@0 | 141 | } |
michael@0 | 142 | return hexrep.join(''); |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | #ifdef DEBUG |
michael@0 | 146 | /** |
michael@0 | 147 | * Lame unittest function |
michael@0 | 148 | */ |
michael@0 | 149 | function TEST_G_CryptoHasher() { |
michael@0 | 150 | if (G_GDEBUG) { |
michael@0 | 151 | var z = "cryptohasher UNITTEST"; |
michael@0 | 152 | G_debugService.enableZone(z); |
michael@0 | 153 | |
michael@0 | 154 | G_Debug(z, "Starting"); |
michael@0 | 155 | |
michael@0 | 156 | var md5 = function(str) { |
michael@0 | 157 | var hasher = new G_CryptoHasher(); |
michael@0 | 158 | hasher.init(G_CryptoHasher.algorithms.MD5); |
michael@0 | 159 | hasher.updateFromString(str); |
michael@0 | 160 | return hasher.digestHex().toLowerCase(); |
michael@0 | 161 | }; |
michael@0 | 162 | |
michael@0 | 163 | // test vectors from: http://www.faqs.org/rfcs/rfc1321.html |
michael@0 | 164 | var vectors = {"": "d41d8cd98f00b204e9800998ecf8427e", |
michael@0 | 165 | "a": "0cc175b9c0f1b6a831c399e269772661", |
michael@0 | 166 | "abc": "900150983cd24fb0d6963f7d28e17f72", |
michael@0 | 167 | "message digest": "f96b697d7cb7938d525a2f31aaf161d0", |
michael@0 | 168 | "abcdefghijklmnopqrstuvwxyz": "c3fcd3d76192e4007dfb496cca67e13b", |
michael@0 | 169 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": "d174ab98d277d9f5a5611c2c9f419d9f", |
michael@0 | 170 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890": "57edf4a22be3c955ac49da2e2107b67a"}; |
michael@0 | 171 | |
michael@0 | 172 | G_Debug(z, "PASSED"); |
michael@0 | 173 | } |
michael@0 | 174 | } |
michael@0 | 175 | #endif |