diff options
Diffstat (limited to 'libphobos/src/std/digest/package.d')
-rw-r--r-- | libphobos/src/std/digest/package.d | 1171 |
1 files changed, 1171 insertions, 0 deletions
diff --git a/libphobos/src/std/digest/package.d b/libphobos/src/std/digest/package.d new file mode 100644 index 0000000..f4646ae --- /dev/null +++ b/libphobos/src/std/digest/package.d @@ -0,0 +1,1171 @@ +/** + * This module describes the _digest APIs used in Phobos. All digests follow + * these APIs. Additionally, this module contains useful helper methods which + * can be used with every _digest type. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF isDigest) $(MYREF DigestType) $(MYREF hasPeek) + $(MYREF hasBlockSize) + $(MYREF ExampleDigest) $(MYREF _digest) $(MYREF hexDigest) $(MYREF makeDigest) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF Digest) +) +) +$(TR $(TDNW Helper functions) $(TD $(MYREF toHexString)) +) +$(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDigest)) +) +) +) + + * APIs: + * There are two APIs for digests: The template API and the OOP API. The template API uses structs + * and template helpers like $(LREF isDigest). The OOP API implements digests as classes inheriting + * the $(LREF Digest) interface. All digests are named so that the template API struct is called "$(B x)" + * and the OOP API class is called "$(B x)Digest". For example we have $(D MD5) <--> $(D MD5Digest), + * $(D CRC32) <--> $(D CRC32Digest), etc. + * + * The template API is slightly more efficient. It does not have to allocate memory dynamically, + * all memory is allocated on the stack. The OOP API has to allocate in the finish method if no + * buffer was provided. If you provide a buffer to the OOP APIs finish function, it doesn't allocate, + * but the $(LREF Digest) classes still have to be created using $(D new) which allocates them using the GC. + * + * The OOP API is useful to change the _digest function and/or _digest backend at 'runtime'. The benefit here + * is that switching e.g. Phobos MD5Digest and an OpenSSLMD5Digest implementation is ABI compatible. + * + * If just one specific _digest type and backend is needed, the template API is usually a good fit. + * In this simplest case, the template API can even be used without templates: Just use the "$(B x)" structs + * directly. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Johannes Pfau + * + * Source: $(PHOBOSSRC std/_digest/_package.d) + * + * CTFE: + * Digests do not work in CTFE + * + * TODO: + * Digesting single bits (as opposed to bytes) is not implemented. This will be done as another + * template constraint helper (hasBitDigesting!T) and an additional interface (BitDigest) + */ +/* Copyright Johannes Pfau 2012. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +module std.digest; + +public import std.ascii : LetterCase; +import std.meta : allSatisfy; +import std.range.primitives; +import std.traits; + + +/// +@system unittest +{ + import std.digest.crc; + + //Simple example + char[8] hexHash = hexDigest!CRC32("The quick brown fox jumps over the lazy dog"); + assert(hexHash == "39A34F41"); + + //Simple example, using the API manually + CRC32 context = makeDigest!CRC32(); + context.put(cast(ubyte[])"The quick brown fox jumps over the lazy dog"); + ubyte[4] hash = context.finish(); + assert(toHexString(hash) == "39A34F41"); +} + +/// +@system unittest +{ + //Generating the hashes of a file, idiomatic D way + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + + // Digests a file and prints the result. + void digestFile(Hash)(string filename) + if (isDigest!Hash) + { + auto file = File(filename); + auto result = digest!Hash(file.byChunk(4096 * 1024)); + writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result)); + } + + void main(string[] args) + { + foreach (name; args[1 .. $]) + { + digestFile!MD5(name); + digestFile!SHA1(name); + digestFile!CRC32(name); + } + } +} +/// +@system unittest +{ + //Generating the hashes of a file using the template API + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + // Digests a file and prints the result. + void digestFile(Hash)(ref Hash hash, string filename) + if (isDigest!Hash) + { + File file = File(filename); + + //As digests imlement OutputRange, we could use std.algorithm.copy + //Let's do it manually for now + foreach (buffer; file.byChunk(4096 * 1024)) + hash.put(buffer); + + auto result = hash.finish(); + writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result)); + } + + void uMain(string[] args) + { + MD5 md5; + SHA1 sha1; + CRC32 crc32; + + md5.start(); + sha1.start(); + crc32.start(); + + foreach (arg; args[1 .. $]) + { + digestFile(md5, arg); + digestFile(sha1, arg); + digestFile(crc32, arg); + } + } +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + + // Digests a file and prints the result. + void digestFile(Digest hash, string filename) + { + File file = File(filename); + + //As digests implement OutputRange, we could use std.algorithm.copy + //Let's do it manually for now + foreach (buffer; file.byChunk(4096 * 1024)) + hash.put(buffer); + + ubyte[] result = hash.finish(); + writefln("%s (%s) = %s", typeid(hash).toString(), filename, toHexString(result)); + } + + void umain(string[] args) + { + auto md5 = new MD5Digest(); + auto sha1 = new SHA1Digest(); + auto crc32 = new CRC32Digest(); + + foreach (arg; args[1 .. $]) + { + digestFile(md5, arg); + digestFile(sha1, arg); + digestFile(crc32, arg); + } + } +} + +version (StdDdoc) + version = ExampleDigest; + +version (ExampleDigest) +{ + /** + * This documents the general structure of a Digest in the template API. + * All digest implementations should implement the following members and therefore pass + * the $(LREF isDigest) test. + * + * Note: + * $(UL + * $(LI A digest must be a struct (value type) to pass the $(LREF isDigest) test.) + * $(LI A digest passing the $(LREF isDigest) test is always an $(D OutputRange)) + * ) + */ + struct ExampleDigest + { + public: + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * The following usages of $(D put) must work for any type which + * passes $(LREF isDigest): + * Example: + * ---- + * ExampleDigest dig; + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * ---- + */ + @trusted void put(scope const(ubyte)[] data...) + { + + } + + /** + * This function is used to (re)initialize the digest. + * It must be called before using the digest and it also works as a 'reset' function + * if the digest has already processed data. + */ + @trusted void start() + { + + } + + /** + * The finish function returns the final hash sum and resets the Digest. + * + * Note: + * The actual type returned by finish depends on the digest implementation. + * $(D ubyte[16]) is just used as an example. It is guaranteed that the type is a + * static array of ubytes. + * + * $(UL + * $(LI Use $(LREF DigestType) to obtain the actual return type.) + * $(LI Use $(LREF digestLength) to obtain the length of the ubyte array.) + * ) + */ + @trusted ubyte[16] finish() + { + return (ubyte[16]).init; + } + } +} + +/// +@system unittest +{ + //Using the OutputRange feature + import std.algorithm.mutation : copy; + import std.digest.md; + import std.range : repeat; + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + auto ctx = makeDigest!MD5(); + copy(oneMillionRange, &ctx); //Note: You must pass a pointer to copy! + assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); +} + +/** + * Use this to check if a type is a digest. See $(LREF ExampleDigest) to see what + * a type must provide to pass this check. + * + * Note: + * This is very useful as a template constraint (see examples) + * + * BUGS: + * $(UL + * $(LI Does not yet verify that put takes scope parameters.) + * $(LI Should check that finish() returns a ubyte[num] array) + * ) + */ +template isDigest(T) +{ + import std.range : isOutputRange; + enum bool isDigest = isOutputRange!(T, const(ubyte)[]) && isOutputRange!(T, ubyte) && + is(T == struct) && + is(typeof( + { + T dig = void; //Can define + dig.put(cast(ubyte) 0, cast(ubyte) 0); //varags + dig.start(); //has start + auto value = dig.finish(); //has finish + })); +} + +/// +@system unittest +{ + import std.digest.crc; + static assert(isDigest!CRC32); +} +/// +@system unittest +{ + import std.digest.crc; + void myFunction(T)() + if (isDigest!T) + { + T dig; + dig.start(); + auto result = dig.finish(); + } + myFunction!CRC32(); +} + +/** + * Use this template to get the type which is returned by a digest's $(LREF finish) method. + */ +template DigestType(T) +{ + static if (isDigest!T) + { + alias DigestType = + ReturnType!(typeof( + { + T dig = void; + return dig.finish(); + })); + } + else + static assert(false, T.stringof ~ " is not a digest! (fails isDigest!T)"); +} + +/// +@system unittest +{ + import std.digest.crc; + assert(is(DigestType!(CRC32) == ubyte[4])); +} +/// +@system unittest +{ + import std.digest.crc; + CRC32 dig; + dig.start(); + DigestType!CRC32 result = dig.finish(); +} + +/** + * Used to check if a digest supports the $(D peek) method. + * Peek has exactly the same function signatures as finish, but it doesn't reset + * the digest's internal state. + * + * Note: + * $(UL + * $(LI This is very useful as a template constraint (see examples)) + * $(LI This also checks if T passes $(LREF isDigest)) + * ) + */ +template hasPeek(T) +{ + enum bool hasPeek = isDigest!T && + is(typeof( + { + T dig = void; //Can define + DigestType!T val = dig.peek(); + })); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md; + assert(!hasPeek!(MD5)); + assert(hasPeek!CRC32); +} +/// +@system unittest +{ + import std.digest.crc; + void myFunction(T)() + if (hasPeek!T) + { + T dig; + dig.start(); + auto result = dig.peek(); + } + myFunction!CRC32(); +} + +/** + * Checks whether the digest has a $(D blockSize) member, which contains the + * digest's internal block size in bits. It is primarily used by $(REF HMAC, std,digest,hmac). + */ + +template hasBlockSize(T) +if (isDigest!T) +{ + enum bool hasBlockSize = __traits(compiles, { size_t blockSize = T.blockSize; }); +} + +/// +@system unittest +{ + import std.digest.hmac, std.digest.md; + static assert(hasBlockSize!MD5 && MD5.blockSize == 512); + static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == 512); +} + +package template isDigestibleRange(Range) +{ + import std.digest.md; + import std.range : isInputRange, ElementType; + enum bool isDigestibleRange = isInputRange!Range && is(typeof( + { + MD5 ha; //Could use any conformant hash + ElementType!Range val; + ha.put(val); + })); +} + +/** + * This is a convenience function to calculate a hash using the template API. + * Every digest passing the $(LREF isDigest) test can be used with this function. + * + * Params: + * range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) + */ +DigestType!Hash digest(Hash, Range)(auto ref Range range) +if (!isArray!Range + && isDigestibleRange!Range) +{ + import std.algorithm.mutation : copy; + Hash hash; + hash.start(); + copy(range, &hash); + return hash.finish(); +} + +/// +@system unittest +{ + import std.digest.md; + import std.range : repeat; + auto testRange = repeat!ubyte(cast(ubyte)'a', 100); + auto md5 = digest!MD5(testRange); +} + +/** + * This overload of the digest function handles arrays. + * + * Params: + * data= one or more arrays of any type + */ +DigestType!Hash digest(Hash, T...)(scope const T data) +if (allSatisfy!(isArray, typeof(data))) +{ + Hash hash; + hash.start(); + foreach (datum; data) + hash.put(cast(const(ubyte[]))datum); + return hash.finish(); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + auto md5 = digest!MD5( "The quick brown fox jumps over the lazy dog"); + auto sha1 = digest!SHA1( "The quick brown fox jumps over the lazy dog"); + auto crc32 = digest!CRC32("The quick brown fox jumps over the lazy dog"); + assert(toHexString(crc32) == "39A34F41"); +} + +/// +@system unittest +{ + import std.digest.crc; + auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); + assert(toHexString(crc32) == "39A34F41"); +} + +/** + * This is a convenience function similar to $(LREF digest), but it returns the string + * representation of the hash. Every digest passing the $(LREF isDigest) test can be used with this + * function. + * + * Params: + * order= the order in which the bytes are processed (see $(LREF toHexString)) + * range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) + */ +char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, Range)(ref Range range) +if (!isArray!Range && isDigestibleRange!Range) +{ + return toHexString!order(digest!Hash(range)); +} + +/// +@system unittest +{ + import std.digest.md; + import std.range : repeat; + auto testRange = repeat!ubyte(cast(ubyte)'a', 100); + assert(hexDigest!MD5(testRange) == "36A92CC94A9E0FA21F625F8BFB007ADF"); +} + +/** + * This overload of the hexDigest function handles arrays. + * + * Params: + * order= the order in which the bytes are processed (see $(LREF toHexString)) + * data= one or more arrays of any type + */ +char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, T...)(scope const T data) +if (allSatisfy!(isArray, typeof(data))) +{ + return toHexString!order(digest!Hash(data)); +} + +/// +@system unittest +{ + import std.digest.crc; + assert(hexDigest!(CRC32, Order.decreasing)("The quick brown fox jumps over the lazy dog") == "414FA339"); +} +/// +@system unittest +{ + import std.digest.crc; + assert(hexDigest!(CRC32, Order.decreasing)("The quick ", "brown ", "fox jumps over the lazy dog") == "414FA339"); +} + +/** + * This is a convenience function which returns an initialized digest, so it's not necessary to call + * start manually. + */ +Hash makeDigest(Hash)() +{ + Hash hash; + hash.start(); + return hash; +} + +/// +@system unittest +{ + import std.digest.md; + auto md5 = makeDigest!MD5(); + md5.put(0); + assert(toHexString(md5.finish()) == "93B885ADFE0DA089CDF634904FD59F71"); +} + +/*+*************************** End of template part, welcome to OOP land **************************/ + +/** + * This describes the OOP API. To understand when to use the template API and when to use the OOP API, + * see the module documentation at the top of this page. + * + * The Digest interface is the base interface which is implemented by all digests. + * + * Note: + * A Digest implementation is always an $(D OutputRange) + */ +interface Digest +{ + public: + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * + * Example: + * ---- + * void test(Digest dig) + * { + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * } + * ---- + */ + @trusted nothrow void put(scope const(ubyte)[] data...); + + /** + * Resets the internal state of the digest. + * Note: + * $(LREF finish) calls this internally, so it's not necessary to call + * $(D reset) manually after a call to $(LREF finish). + */ + @trusted nothrow void reset(); + + /** + * This is the length in bytes of the hash value which is returned by $(LREF finish). + * It's also the required size of a buffer passed to $(LREF finish). + */ + @trusted nothrow @property size_t length() const; + + /** + * The finish function returns the hash value. It takes an optional buffer to copy the data + * into. If a buffer is passed, it must be at least $(LREF length) bytes big. + */ + @trusted nothrow ubyte[] finish(); + ///ditto + nothrow ubyte[] finish(ubyte[] buf); + //@@@BUG@@@ http://d.puremagic.com/issues/show_bug.cgi?id=6549 + /*in + { + assert(buf.length >= this.length); + }*/ + + /** + * This is a convenience function to calculate the hash of a value using the OOP API. + */ + final @trusted nothrow ubyte[] digest(scope const(void[])[] data...) + { + this.reset(); + foreach (datum; data) + this.put(cast(ubyte[]) datum); + return this.finish(); + } +} + +/// +@system unittest +{ + //Using the OutputRange feature + import std.algorithm.mutation : copy; + import std.digest.md; + import std.range : repeat; + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + auto ctx = new MD5Digest(); + copy(oneMillionRange, ctx); + assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + ubyte[] md5 = (new MD5Digest()).digest("The quick brown fox jumps over the lazy dog"); + ubyte[] sha1 = (new SHA1Digest()).digest("The quick brown fox jumps over the lazy dog"); + ubyte[] crc32 = (new CRC32Digest()).digest("The quick brown fox jumps over the lazy dog"); + assert(crcHexString(crc32) == "414FA339"); +} + +/// +@system unittest +{ + import std.digest.crc; + ubyte[] crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog"); + assert(crcHexString(crc32) == "414FA339"); +} + +@system unittest +{ + import std.range : isOutputRange; + assert(!isDigest!(Digest)); + assert(isOutputRange!(Digest, ubyte)); +} + +/// +@system unittest +{ + void test(Digest dig) + { + dig.put(cast(ubyte) 0); //single ubyte + dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + ubyte[10] buf; + dig.put(buf); //buffer + } +} + +/*+*************************** End of OOP part, helper functions follow ***************************/ + +/** + * See $(LREF toHexString) + */ +enum Order : bool +{ + increasing, /// + decreasing /// +} + + +/** + * Used to convert a hash value (a static or dynamic array of ubytes) to a string. + * Can be used with the OOP and with the template API. + * + * The additional order parameter can be used to specify the order of the input data. + * By default the data is processed in increasing order, starting at index 0. To process it in the + * opposite order, pass Order.decreasing as a parameter. + * + * The additional letterCase parameter can be used to specify the case of the output data. + * By default the output is in upper case. To change it to the lower case + * pass LetterCase.lower as a parameter. + * + * Note: + * The function overloads returning a string allocate their return values + * using the GC. The versions returning static arrays use pass-by-value for + * the return value, effectively avoiding dynamic allocation. + */ +char[num*2] toHexString(Order order = Order.increasing, size_t num, LetterCase letterCase = LetterCase.upper) +(in ubyte[num] digest) +{ + static if (letterCase == LetterCase.upper) + { + import std.ascii : hexDigits = hexDigits; + } + else + { + import std.ascii : hexDigits = lowerHexDigits; + } + + + char[num*2] result; + size_t i; + + static if (order == Order.increasing) + { + foreach (u; digest) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + else + { + size_t j = num - 1; + while (i < num*2) + { + result[i++] = hexDigits[digest[j] >> 4]; + result[i++] = hexDigits[digest[j] & 15]; + j--; + } + } + return result; +} + +///ditto +char[num*2] toHexString(LetterCase letterCase, Order order = Order.increasing, size_t num)(in ubyte[num] digest) +{ + return toHexString!(order, num, letterCase)(digest); +} + +///ditto +string toHexString(Order order = Order.increasing, LetterCase letterCase = LetterCase.upper) +(in ubyte[] digest) +{ + static if (letterCase == LetterCase.upper) + { + import std.ascii : hexDigits = hexDigits; + } + else + { + import std.ascii : hexDigits = lowerHexDigits; + } + + auto result = new char[digest.length*2]; + size_t i; + + static if (order == Order.increasing) + { + foreach (u; digest) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + else + { + import std.range : retro; + foreach (u; retro(digest)) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + import std.exception : assumeUnique; + // memory was just created, so casting to immutable is safe + return () @trusted { return assumeUnique(result); }(); +} + +///ditto +string toHexString(LetterCase letterCase, Order order = Order.increasing)(in ubyte[] digest) +{ + return toHexString!(order, letterCase)(digest); +} + +//For more example unittests, see Digest.digest, digest + +/// +@safe unittest +{ + import std.digest.crc; + //Test with template API: + auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); + //Lower case variant: + assert(toHexString!(LetterCase.lower)(crc32) == "39a34f41"); + //Usually CRCs are printed in this order, though: + assert(toHexString!(Order.decreasing)(crc32) == "414FA339"); + assert(toHexString!(LetterCase.lower, Order.decreasing)(crc32) == "414fa339"); +} + +/// +@safe unittest +{ + import std.digest.crc; + // With OOP API + auto crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog"); + //Usually CRCs are printed in this order, though: + assert(toHexString!(Order.decreasing)(crc32) == "414FA339"); +} + +@safe unittest +{ + ubyte[16] data; + assert(toHexString(data) == "00000000000000000000000000000000"); + + assert(toHexString(cast(ubyte[4])[42, 43, 44, 45]) == "2A2B2C2D"); + assert(toHexString(cast(ubyte[])[42, 43, 44, 45]) == "2A2B2C2D"); + assert(toHexString!(Order.decreasing)(cast(ubyte[4])[42, 43, 44, 45]) == "2D2C2B2A"); + assert(toHexString!(Order.decreasing, LetterCase.lower)(cast(ubyte[4])[42, 43, 44, 45]) == "2d2c2b2a"); + assert(toHexString!(Order.decreasing)(cast(ubyte[])[42, 43, 44, 45]) == "2D2C2B2A"); +} + +/*+*********************** End of public helper part, private helpers follow ***********************/ + +/* + * Used to convert from a ubyte[] slice to a ref ubyte[N]. + * This helper is used internally in the WrapperDigest template to wrap the template API's + * finish function. + */ +ref T[N] asArray(size_t N, T)(ref T[] source, string errorMsg = "") +{ + assert(source.length >= N, errorMsg); + return *cast(T[N]*) source.ptr; +} + +/* + * Returns the length (in bytes) of the hash value produced by T. + */ +template digestLength(T) +if (isDigest!T) +{ + enum size_t digestLength = (ReturnType!(T.finish)).length; +} + +@safe pure nothrow @nogc +unittest +{ + import std.digest.md : MD5; + import std.digest.sha : SHA1, SHA256, SHA512; + assert(digestLength!MD5 == 16); + assert(digestLength!SHA1 == 20); + assert(digestLength!SHA256 == 32); + assert(digestLength!SHA512 == 64); +} + +/** + * Wraps a template API hash struct into a Digest interface. + * Modules providing digest implementations will usually provide + * an alias for this template (e.g. MD5Digest, SHA1Digest, ...). + */ +class WrapperDigest(T) +if (isDigest!T) : Digest +{ + protected: + T _digest; + + public final: + /** + * Initializes the digest. + */ + this() + { + _digest.start(); + } + + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + */ + @trusted nothrow void put(scope const(ubyte)[] data...) + { + _digest.put(data); + } + + /** + * Resets the internal state of the digest. + * Note: + * $(LREF finish) calls this internally, so it's not necessary to call + * $(D reset) manually after a call to $(LREF finish). + */ + @trusted nothrow void reset() + { + _digest.start(); + } + + /** + * This is the length in bytes of the hash value which is returned by $(LREF finish). + * It's also the required size of a buffer passed to $(LREF finish). + */ + @trusted nothrow @property size_t length() const pure + { + return digestLength!T; + } + + /** + * The finish function returns the hash value. It takes an optional buffer to copy the data + * into. If a buffer is passed, it must have a length at least $(LREF length) bytes. + * + * Example: + * -------- + * + * import std.digest.md; + * ubyte[16] buf; + * auto hash = new WrapperDigest!MD5(); + * hash.put(cast(ubyte) 0); + * auto result = hash.finish(buf[]); + * //The result is now in result (and in buf). If you pass a buffer which is bigger than + * //necessary, result will have the correct length, but buf will still have it's original + * //length + * -------- + */ + nothrow ubyte[] finish(ubyte[] buf) + in + { + assert(buf.length >= this.length); + } + body + { + enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ + "big, check " ~ typeof(this).stringof ~ ".length!"; + asArray!(digestLength!T)(buf, msg) = _digest.finish(); + return buf[0 .. digestLength!T]; + } + + ///ditto + @trusted nothrow ubyte[] finish() + { + enum len = digestLength!T; + auto buf = new ubyte[len]; + asArray!(digestLength!T)(buf) = _digest.finish(); + return buf; + } + + version (StdDdoc) + { + /** + * Works like $(D finish) but does not reset the internal state, so it's possible + * to continue putting data into this WrapperDigest after a call to peek. + * + * These functions are only available if $(D hasPeek!T) is true. + */ + @trusted ubyte[] peek(ubyte[] buf) const; + ///ditto + @trusted ubyte[] peek() const; + } + else static if (hasPeek!T) + { + @trusted ubyte[] peek(ubyte[] buf) const + in + { + assert(buf.length >= this.length); + } + body + { + enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ + "big, check " ~ typeof(this).stringof ~ ".length!"; + asArray!(digestLength!T)(buf, msg) = _digest.peek(); + return buf[0 .. digestLength!T]; + } + + @trusted ubyte[] peek() const + { + enum len = digestLength!T; + auto buf = new ubyte[len]; + asArray!(digestLength!T)(buf) = _digest.peek(); + return buf; + } + } +} + +/// +@system unittest +{ + import std.digest.md; + //Simple example + auto hash = new WrapperDigest!MD5(); + hash.put(cast(ubyte) 0); + auto result = hash.finish(); +} + +/// +@system unittest +{ + //using a supplied buffer + import std.digest.md; + ubyte[16] buf; + auto hash = new WrapperDigest!MD5(); + hash.put(cast(ubyte) 0); + auto result = hash.finish(buf[]); + //The result is now in result (and in buf). If you pass a buffer which is bigger than + //necessary, result will have the correct length, but buf will still have it's original + //length +} + +@safe unittest +{ + // Test peek & length + import std.digest.crc; + auto hash = new WrapperDigest!CRC32(); + assert(hash.length == 4); + hash.put(cast(const(ubyte[]))"The quick brown fox jumps over the lazy dog"); + assert(hash.peek().toHexString() == "39A34F41"); + ubyte[5] buf; + assert(hash.peek(buf).toHexString() == "39A34F41"); +} + +/** + * Securely compares two digest representations while protecting against timing + * attacks. Do not use `==` to compare digest representations. + * + * The attack happens as follows: + * + * $(OL + * $(LI An attacker wants to send harmful data to your server, which + * requires a integrity HMAC SHA1 token signed with a secret.) + * $(LI The length of the token is known to be 40 characters long due to its format, + * so the attacker first sends `"0000000000000000000000000000000000000000"`, + * then `"1000000000000000000000000000000000000000"`, and so on.) + * $(LI The given HMAC token is compared with the expected token using the + * `==` string comparison, which returns `false` as soon as the first wrong + * element is found. If a wrong element is found, then a rejection is sent + * back to the sender.) + * $(LI Eventually, the attacker is able to determine the first character in + * the correct token because the sever takes slightly longer to return a + * rejection. This is due to the comparison moving on to second item in + * the two arrays, seeing they are different, and then sending the rejection.) + * $(LI It may seem like too small of a difference in time for the attacker + * to notice, but security researchers have shown that differences as + * small as $(LINK2 http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf, + * 20µs can be reliably distinguished) even with network inconsistencies.) + * $(LI Repeat the process for each character until the attacker has the whole + * correct token and the server accepts the harmful data. This can be done + * in a week with the attacker pacing the attack to 10 requests per second + * with only one client.) + * ) + * + * This function defends against this attack by always comparing every single + * item in the array if the two arrays are the same length. Therefore, this + * function is always $(BIGOH n) for ranges of the same length. + * + * This attack can also be mitigated via rate limiting and banning IPs which have too + * many rejected requests. However, this does not completely solve the problem, + * as the attacker could be in control of a bot net. To fully defend against + * the timing attack, rate limiting, banning IPs, and using this function + * should be used together. + * + * Params: + * r1 = A digest representation + * r2 = A digest representation + * Returns: + * `true` if both representations are equal, `false` otherwise + * See_Also: + * $(LINK2 https://en.wikipedia.org/wiki/Timing_attack, The Wikipedia article + * on timing attacks). + */ +bool secureEqual(R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2 && !isInfinite!R1 && !isInfinite!R2 && + (isIntegral!(ElementEncodingType!R1) || isSomeChar!(ElementEncodingType!R1)) && + !is(CommonType!(ElementEncodingType!R1, ElementEncodingType!R2) == void)) +{ + static if (hasLength!R1 && hasLength!R2) + if (r1.length != r2.length) + return false; + + int result; + + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && + hasLength!R1 && hasLength!R2) + { + foreach (i; 0 .. r1.length) + result |= r1[i] ^ r2[i]; + } + else static if (hasLength!R1 && hasLength!R2) + { + // Lengths are the same so we can squeeze out a bit of performance + // by not checking if r2 is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + result |= r1.front ^ r2.front; + } + } + else + { + // Generic case, walk both ranges + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty) return false; + result |= r1.front ^ r2.front; + } + if (!r2.empty) return false; + } + + return result == 0; +} + +/// +@system pure unittest +{ + import std.digest.hmac : hmac; + import std.digest.sha : SHA1; + import std.string : representation; + + // a typical HMAC data integrity verification + auto secret = "A7GZIP6TAQA6OHM7KZ42KB9303CEY0MOV5DD6NTV".representation; + auto data = "data".representation; + + string hex1 = data.hmac!SHA1(secret).toHexString; + string hex2 = data.hmac!SHA1(secret).toHexString; + string hex3 = "data1".representation.hmac!SHA1(secret).toHexString; + + assert( secureEqual(hex1, hex2)); + assert(!secureEqual(hex1, hex3)); +} + +@system pure unittest +{ + import std.internal.test.dummyrange : ReferenceInputRange; + import std.range : takeExactly; + import std.string : representation; + import std.utf : byWchar, byDchar; + + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E61077".representation; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".representation; + assert(!secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E610779018"w.representation; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018"d.representation; + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byWchar; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byDchar; + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E61077".byWchar; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byDchar; + assert(!secureEqual(hex1, hex2)); + } + { + auto hex1 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + auto hex2 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + auto hex2 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 9]).takeExactly(9); + assert(!secureEqual(hex1, hex2)); + } +} |