/** * 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) $(MYREF secureEqual)) ) $(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 `MD5` <--> `MD5Digest`, * `CRC32` <--> `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 `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 `OutputRange`) * ) */ struct ExampleDigest { public: /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) * interface for `ubyte` and `const(ubyte)[]`. * The following usages of `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. * `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 `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 `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 `InputRange` with `ElementType` `ubyte`, `ubyte[]` or `ubyte[num]` */ DigestType!Hash digest(Hash, Range)(auto ref Range range) if (!isArray!Range && isDigestibleRange!Range) { Hash hash; hash.start(); alias E = ElementType!Range; // Not necessarily ubyte. Could be ubyte[N] or ubyte[] or something w/alias this. static if (!(__traits(isScalar, E) && E.sizeof == 1)) { foreach (e; range) hash.put(e); return hash.finish(); } else { static if (hasBlockSize!Hash) enum bufferBytes = Hash.blockSize >= (8192 * 8) ? 8192 : Hash.blockSize <= 64 ? 8 : (Hash.blockSize / 8); else enum bufferBytes = 8; ubyte[bufferBytes] buffer = void; static if (isRandomAccessRange!Range && hasLength!Range) { const end = range.length; size_t i = 0; while (end - i >= buffer.length) { foreach (ref e; buffer) e = range[i++]; hash.put(buffer); } if (const remaining = end - i) { foreach (ref e; buffer[0 .. remaining]) e = range[i++]; hash.put(buffer[0 .. remaining]); } return hash.finish(); } else { for (;;) { size_t n = buffer.length; foreach (i, ref e; buffer) { if (range.empty) { n = i; break; } e = range.front; range.popFront(); } if (n) hash.put(buffer[0 .. n]); if (n != buffer.length) 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 `InputRange` with `ElementType` `ubyte`, `ubyte[]` or `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 `OutputRange` */ interface Digest { public: /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) * interface for `ubyte` and `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 * `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); // https://issues.dlang.org/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 /// } /// @safe unittest { import std.digest.crc : CRC32; auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); assert(crc32.toHexString!(Order.decreasing) == "414FA339"); assert(crc32.toHexString!(LetterCase.lower, Order.decreasing) == "414fa339"); } /** * 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) (const ubyte[num] digest) { char[num*2] result; size_t i; toHexStringImpl!(order, letterCase)(digest, result); 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) { auto result = new char[digest.length*2]; toHexStringImpl!(order, letterCase)(digest, result); 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; } /* * Fill in a preallocated buffer with the ASCII hex representation from a byte buffer */ private void toHexStringImpl(Order order, LetterCase letterCase, BB, HB) (scope const ref BB byteBuffer, ref HB hexBuffer){ static if (letterCase == LetterCase.upper) { import std.ascii : hexDigits = hexDigits; } else { import std.ascii : hexDigits = lowerHexDigits; } size_t i; static if (order == Order.increasing) { foreach (u; byteBuffer) { hexBuffer[i++] = hexDigits[u >> 4]; hexBuffer[i++] = hexDigits[u & 15]; } } else { size_t j = byteBuffer.length -1; while (i < byteBuffer.length*2) { hexBuffer[i++] = hexDigits[byteBuffer[j] >> 4]; hexBuffer[i++] = hexDigits[byteBuffer[j] & 15]; j--; } } } /* * 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 `ubyte` and `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 * `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, "Given buffer is smaller than the local buffer."); } do { 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 `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 `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, "Given buffer is smaller than the local buffer."); } do { 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; auto hex1 = data.hmac!SHA1(secret).toHexString; auto hex2 = data.hmac!SHA1(secret).toHexString; auto 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)); } }