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