Cryptographic Practices
Let's make the first statement as strong as your cryptography should be: hashing and encrypting are two different things.
There's a general misconception and most of the time hashing and encrypting are used interchangeably, incorrectly. They are different concepts and they also serve different purposes.
A hash is a string or number generated by a (hash) function from source data:
hash := F(data)
The hash has fixed length and its value vary widely with small variations in input (collisions may still happen). A good hashing algorithm won't allow to turn a hash into its original source1. MD5 is the most popular hashing algorithm but securitywise BLAKE2 is considered the strongest and most flexible.
Go supplementary cryptography libraries offers both BLAKE2b (or just BLAKE2) and BLAKE2s implementations: the former is optimized for 64-bit platforms and the later for 8- to 32-bit platforms. If BLAKE2 is unavailable, SHA-256 is the right option.
Whenever you have something that you don't need to know what it is but only if it is what it is supposed to be (like checking file integrity after download), you should use hashing2
package main
import "fmt"
import "io"
import "crypto/md5"
import "crypto/sha256"
import "golang.org/x/crypto/blake2s"
func main () {
h_md5 := md5.New()
h_sha := sha256.New()
h_blake2s, _ := blake2s.New256(nil)
io.WriteString(h_md5, "Welcome to Go Language Secure Coding Practices")
io.WriteString(h_sha, "Welcome to Go Language Secure Coding Practices")
io.WriteString(h_blake2s, "Welcome to Go Language Secure Coding Practices")
fmt.Printf("MD5 : %x\n", h_md5.Sum(nil))
fmt.Printf("SHA256 : %x\n", h_sha.Sum(nil))
fmt.Printf("Blake2s-256: %x\n", h_blake2s.Sum(nil))
}
The output
MD5 : ea9321d8fb0ec6623319e49a634aad92
SHA256 : ba4939528707d791242d1af175e580c584dc0681af8be2a4604a526e864449f6
Blake2s-256: 1d65fa02df8a149c245e5854d980b38855fd2c78f2924ace9b64e8b21b3f2f82
Note: to run the source code sample you'll need to run
$ go get golang.org/x/crypto/blake2s
On the other hand, encryption turns data into variable length data using a key
encrypted_data := F(data, key)
Unlike the hash, we can compute data
back from encrypted_data
applying the
right decryption function and key
data := F⁻¹(encrypted_data, key)
Encryption should be used whenever you need to communicate or store sensitive data, which you or someone else needs to access later on for further processing. A "simple" encryption use case is the HTTPS - Hyper Text Transfer Protocol Secure. AES is the de facto standard when it comes to symmetric key encryption. This algorithm, as many other symmetric ciphers, can be implemented in different modes. You'll notice in the code sample below, GCM (Galois Counter Mode) was used, instead of the more popular (in cryptography code examples, at least) CBC/ECB. The main difference between GCM and CBC/ECB is the fact that the former is an authenticated cipher mode, meaning that after the encryption stage, an authentication tag is added to the ciphertext, which will then be validated prior to message decryption, ensuring the message has not been tampered with. On the other hand, you have Public key cryptography or asymmetric cryptography which makes use of pairs of keys: public and private. Public key cryptography is less performant than symmetric key cryptography for most cases, so its most common use-case is sharing a symmetric key between two parties using assymetric cryptography, so they can then use the symmetric key to exchange messages encrypted with symmetric cryptography. Aside from AES, which is 90's technology, Go authors have begun to implement and support more modern symmetric encryption algorithms which also provide authentication, such as chacha20poly1305.
Another interesting package in Go is x/crypto/nacl. This is a reference to Dr. Daniel J. Bernstein's NaCl library, which is a very popular modern cryptography library. The nacl/box and nacl/secretbox in Go are implementations of NaCl's abstractions for sending encrypted messages for the two most common use-cases:
- Sending authenticated, encrypted messages between two parties using public key cryptography (nacl/box)
- Sending authenticated, encrypted messages between two parties using symmetric (a.k.a secret-key) cryptography
It is very advisable to use one of these abstractions instead of direct use of AES, if they fit your use-case.
package main
import "fmt"
import "crypto/aes"
import "crypto/cipher"
import "crypto/rand"
func main() {
key := []byte("Encryption Key should be 32 char")
data := []byte("Welcome to Go Language Secure Coding Practices")
block, err := aes.NewCipher(key)
if err != nil {
panic(err.Error())
}
nonce := make([]byte, 12)
if _, err := rand.Read(nonce); err != nil {
panic(err.Error())
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
encrypted_data := aesgcm.Seal(nil, nonce, data, nil)
fmt.Printf("Encrypted: %x\n", encrypted_data)
decrypted_data, err := aesgcm.Open(nil, nonce, encrypted_data, nil)
if err != nil {
panic(err.Error())
}
fmt.Printf("Decrypted: %s\n", decrypted_data)
}
Encrypted: a66bd44db1fac7281c33f6ca40494a320644584d0595e5a0e9a202f8aeb22dae659dc06932d4e409fe35a95d14b1cffacbe3914460dd27cbd274b0c3a561
Decrypted: Welcome to Go Language Secure Coding Practices
Please note you should "establish and utilize a policy and process for how cryptographic keys will be managed", protecting "master secrets from unauthorized access". That being said: your cryptographic keys shouldn't be hardcoded in the source code (as it is on this example).
Go's crypto package collects common cryptographic constants, but implementations have their own packages, as the crypto/md5 one.
Most modern cryptographic algorthims have been implemented under
https://godoc.org/golang.org/x/crypto, so developers should focus on those
instead of the implementations in the crypto/*
package.
1. Rainbow table attacks are not a weakness on the hashing algorithms. ↩
2. Consider reading the Authentication and Password Management section about "strong one-way salted hashes" for credentials. ↩