Specifically, we’re going to take a closer look into the world of BLAKE2b and BLAKE2s implementation in C.
Now, before you start running for the hills or reaching for the nearest bottle of whiskey, let me assure you that this isn’t as complicated as it sounds. In fact, with a little bit of guidance and some basic programming knowledge, anyone can become a BLAKE2b/s master!
To start what is BLAKE2? It’s a cryptographic hash function designed by Jean-Pierre Aumasson, Leandro Lavigne, Daniel J. Bernstein, and Juan A. Garay in 2012. The main goal of this algorithm was to create an alternative to MD5/SHA-1 that is more secure and efficient. And let me tell you, they did a pretty good job!
Now, there are two versions of BLAKE2 BLAKE2b (for 64-bit platforms) and BLAKE2s (for 32-bit platforms). The main difference between the two is that BLAKE2b uses larger state vectors and produces a longer output hash. But don’t worry, we won’t get into too much detail about that let’s focus on how to implement them in C!
First, let’s take a look at the header files for both functions: blake2b.h and blake2s.h. These headers contain all of the necessary definitions and macros needed to use BLAKE2b/s in your code.
Next, we need to include these headers in our C file (let’s call it main.c) like so:
// This script includes the necessary header files for using BLAKE2b/s in C.
// The blake2b.h and blake2s.h headers contain definitions and macros needed for the functions.
#include "blake2b.h"
// or #include "blake2s.h", depending on which version you want to use!
// This is the main function where the code will be executed.
int main() {
// Here we declare a variable to store the result of the BLAKE2b/s function.
// We use the uint8_t data type to represent a byte.
uint8_t result[64];
// We call the blake2b function, passing in the result variable, the size of the result (64 bytes),
// and any additional parameters we want to use.
// In this case, we are using the default parameters.
blake2b(result, 64, NULL, 0, NULL, 0);
// We can now use the result variable to access the output of the BLAKE2b function.
// For example, we can print it out to the console.
printf("The result of the BLAKE2b function is: ");
for (int i = 0; i < 64; i++) {
printf("%02x", result[i]); // %02x is used to print the result in hexadecimal format.
}
printf("\n");
// Similarly, we can use the blake2s function to calculate the BLAKE2s hash.
// We use a different result variable and specify the size of the result (32 bytes).
uint8_t result2[32];
blake2s(result2, 32, NULL, 0, NULL, 0);
// We can print out the result of the BLAKE2s function in the same way.
printf("The result of the BLAKE2s function is: ");
for (int i = 0; i < 32; i++) {
printf("%02x", result2[i]);
}
printf("\n");
return 0; // We end the main function by returning 0 to indicate successful execution.
}
Now, let’s take a look at the actual implementation of BLAKE2b and BLAKE2s in C. Here’s an example code snippet for blake2b:
// This for loop is used to convert and store the output of the BLAKE2b algorithm in little endian format.
for (i = 0; i < ctx->outlen; i++) {
// The output is stored in the "out" variable, which is a pointer to a uint8_t type.
// The loop iterates through each byte of the output.
// The value of "i" is used as an index to access the corresponding byte in the output.
// The value of "ctx->outlen" is the length of the output in bytes.
// The loop will continue until "i" reaches the length of the output.
// The value of "i & 7" is used to determine the position of the current byte within a 64-bit block.
// This is done by performing a bitwise AND operation with 7, which is equivalent to modulo 8.
// This ensures that the correct byte is accessed within each 64-bit block.
// The value of "ctx->h[i + 3]" is used to access the corresponding 64-bit block in the hash state.
// The value of "i + 3" is used as an index to access the next 64-bit block in the hash state.
// This ensures that the correct 64-bit block is accessed for each byte of the output.
// The value of "8 * (i & 7)" is used to determine the number of bits to shift the 64-bit block.
// This is done by multiplying the current byte position by 8, which is equivalent to shifting left by 3 bits.
// This ensures that the correct bits are extracted from the 64-bit block.
// The value of "ctx->h[i + 3] >> (8 * (i & 7))" is used to shift the 64-bit block to the right by the correct number of bits.
// This ensures that the correct bits are extracted from the 64-bit block.
// The value of "& 0xFF" is used to mask the extracted bits to only the least significant 8 bits.
// This ensures that only the correct bits are stored in the output.
((uint8_t *) out)[i] = (ctx->h[i + 3] >> (8 * (i & 7))) & 0xFF;
}
This code snippet is used to convert the final hash output from a big-endian format to little-endian for easier storage and transmission. The `ctx->h` array contains the intermediate state vectors, which are then shifted and masked using bitwise operations to produce the final hash value.
Now, let’s take a look at how we can use these functions in our code! Here’s an example of how you might call blake2b:
#include <stdio.h> // include standard input/output library
#include <string.h> // include string library
#include "blake2.h" // include blake2 hashing library
int main() {
const char *input = "Hello, world!"; // the input data to hash
unsigned int keylen = 0; // no secret key for this example
uint8_t output[BLAKE2B_OUTBYTES]; // buffer to store the final hash value
if (blake2b(output, BLAKE2B_OUTBYTES, NULL, keylen, input, strlen(input)) != 0) { // call blake2b function to hash input data
printf("Error: blake2b failed!\n"); // print error message if function fails
return -1; // return -1 to indicate failure
}
// output is now the final hash value!
}
And that’s it! You can use this same code snippet for BLAKE2s, just replace `blake2b()` with `blake2s()`.