Cryptography with Go: A Focus on Blockchain

Introduction

Computer security has always been an important concept since more people became consumers. The use of cryptography is to drive for more security with files and processes from harmful threats.

Threat modeling is the act of addressing points of attack that threat actors will exploit in a system. Assuming you have a system that interacts with a database, a threat actor would want to try an SQL injection attack by querying the data stored in the database.

Cryptography is a way to secure the data by converting it into unreadable content. Cryptography has seen more recent adoption in the Blockchain space. In this article, you will learn about cryptography and how to implement some types of cryptography in Go.

Table of Contents

  • What Cryptography Is
  • The Standard Crypto Library in Go

What Cryptography Is

Cryptography is an automated algorithmic method of securing files, processes, or communications that are private or exposed to the public.

The following are the goals of cryptography:

  • privacy
  • integrity
  • authorization
  • nonrepudiation

Privacy

Privacy is the act of concealing a message. It does this to prevent it from getting to undesired parties via a medium of information transmission. Privacy is often implemented with symmetric key ciphers — algorithms that accept a secret key, encrypt the message, then turn it into ciphertext.

Integrity

Integrity is the act of ensuring correctness in the absence of an attacker. Integrity is often implemented using one-way hash functions. These functions receive input as an arbitrary length message and produce a fixed size message digest. The digest ranges in size from 160 to 512 bits and is a representation of the message.

Hashes are not keyed algorithms so have no secret information that attackers may target.

Authentication

Authentication is the act of assigning an identity to the integrity of a message like the use of wax seals on letters. Authentication takes the form of personal identification numbers (PINs) and passwords to authorize processes and access.

Nonrepudiation

Nonrepudiation is the act of agreeing to abide by a rule. It means not being able to neglect or refute responsibility. It is common in billing and accounting — very few processes outside these adopt nonrepudiation.

The Standard Crypto Library in Go

Major cryptographic algorithms and features you may want to implement using Go are readily available in the crypto package. There is no need for installing dependencies. However, there are two packages dedicated to cryptography in Go:

The latter contains more hashing algorithms, encryption ciphers, and several utilities for hashing passwords and data.

Data Encryption in Cryptography with Go

Encryption is a two-way cryptographic function that scrambles data in the first step and unscrambles it in the second step (called decryption) when the data is needed.

Encryption can be done in two ways:

  • symmetric encryption algorithm — uses the same key for encryption and decryption
  • asymmetric encryption algorithm — uses different keys for encryption and decryption called a private and a public key pair

A very common example of a symmetric encryption algorithm is the AES (formerly Rijndael) commonly used for files of all kinds and sizes. A common asymmetric algorithm is the RSA commonly used for SSL and SSH keys.

Symmetric Advanced Encryption Standards (AES) in Go

In Go, AES generates a key, performs encryption or decryption, then gives the output in io.Stdout() or the terminal. You can, however, direct the output to another location — like a file or a code — using the > operator.

In the code below, we will implement AES encryption algorithm:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

// generateKey
func generateKey() []byte {
    randomByte := make([]byte, 32)
    numBytesRead, err := rand.Read(randomByte)
    if err != nil {
        log.Fatal("Error generating random key.", err)
    }
    if numBytesRead != 32 {
        log.Fatal("Error generating 32 random bytes for key.")
    }
    return randomByte
}

func encrypt(key, message []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    cipherText := make([]bytem aes.BlockSize+len(message))

    initVectorNonce := cipherText[:aes.BlockSize]
    _, err = io.ReadFull(rand.Reader, initVectorNonce)
    if err != nil {
        return nil, err
    }

    cipherFeedBack := cipher.NewCFBEncrypter(block, initVectorNonce)
    cipherFeedBack.XORKeyStream(cipherText[aes.BlockSize:], message)

    return cipherText, nil
}

func decrypt(key, cipherText []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    initVectorNonce := cipherText[:aes.BlockSize]
    cipherText = cipherText[aes.BlockSize]

    cipher := cipher.NewCFBDecrypter(block, initVectorNonce)
    cipher.XORKeyStream(cipherText, cipherText)

    return cipherText, nil
}

func main() {
    keyFileDate, err := ioutil.ReadFile(keyFile)
    if err != nil {
        log.Fatal("Unable to read key file contents.", err)
    }

    fileData, err := ioutil.ReadFile(file)
    if err != nil {
        log.Fatal("Unable to read key file contents.", err)
    }

    if decryptFlag {
        message, err := decrypt(keyFileDate, fileData)
        if err != nil {
            log.Fatal("Error decrypting.", err)
        }
        fmt.Printf("%s", message)
    } else {
        cipherText, err := encrypt(keyFileDate, fileData)
        if err != nil {
            log.Fatal("Error encrypting. ", err)
        }
        fmt.Printf("%s", cipherText)
    }
}

Asymmetric Rivest-Shamir-Adleman Keys

RSA and ECDSA algorithms are available in the Go standard library. While ECDSA is more secure, RSA is the most common algorithm for SSL Certifications.

In asymmetric RSA, you can only encrypt things smaller than the key size, which is frequently 2048 bits.

Generating a Public and Private Key Pair

Before using asymmetric encryption, public and private key pair are needed. The private key must be kept confidential, and the public key shared.

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "fmt"
)

func generateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey) {
    privateKey, err := rsa.GenerateKey(rand.Reader, bits)
    if err != nil {
        fmt.Println("Error: ", err)
    }
    return privateKey, &privateKey.PublicKey
}

func main()  {
    privateKey, publicKey := generateKeyPair(2048)
    fmt.Printf("Private Key: %v\n", privateKey)
    fmt.Printf("Public Key: %v\n", publicKey)
}

Running this provides a private and a public key. Next, we will save the key as a PEM format so it can be used from a file, with the Go package encoding/pem.

The public key is part of the private key struct hence only one needs saving. The file must remain confidential, however.

package main

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
)

func exportPubKeyAsPEMString(publickey *rsa.PublicKey) string {
    publickeyPEM := string(pem.EncodeToMemory(
        &pem.Block{
            Type:  "RSA PUBLIC KEY",
            Bytes: x509.MarshalPKCS1PublicKey(publickey),
        },
    ))
    return publickeyPEM
}

func exportPrivKeyAsPEMString(privatekey *rsa.PrivateKey) string {
    privatekeyPEM := string(pem.EncodeToMemory(
        &pem.Block{
            Type:  "RSA PRIVATE KEY",
            Bytes: x509.MarshalPKCS1PrivateKey(privatekey),
        },
    ))
    return privatekeyPEM
}

func saveKeyToAFile(keyPEM, filename string) {
    pemBytes := []byte(keyPEM)
    err := ioutil.WriteFile(filename, pemBytes, 0400)
    if err != nil {
        return
    }
}

func main() {
    privateKey, publicKey := generateKeyPair(2048)
    fmt.Printf("Private key: %v\n", privateKey)
    fmt.Printf("Public key: %v\n", publicKey)

    privateKeyString := exportPrivKeyAsPEMString(privateKey)
    publicKeyString := exportPubKeyAsPEMString(publicKey)

    fmt.Println(privateKeyString)
    fmt.Println(publicKeyString)

    saveKeyToAFile(privateKeyString, "privatekey.pem")
    saveKeyToAFile(publicKeyString, "publickey.pem")
}

Next, let’s retrieve the keys from the files using the crypto/x509 and encoding/pem packages:

package main

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
)

func exportPEMStringToPrivateKey(privatekey []byte) *rsa.PrivateKey {
    block, _ := pem.Decode(privatekey)
    key, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
    return key
}

func exportPEMStringToPublicKey(publickey []byte) *rsa.PublicKey {
    block, _ := pem.Decode(publickey)
    key, _ := x509.ParsePKCS1PublicKey(block.Bytes)
    return key
}

func readKeyFromFile(filename string) []byte {
    key, _ := ioutil.ReadFile(filename)
    return key
}

func main() {
    privateKeyPEM := readKeyFromFile("privatekey.pem")
    privateKeyFile := exportPEMStringToPrivateKey(privateKeyPEM)
    fmt.Printf("Private Key: %v\n", privateKeyFile)
}

After this, the encryption and the decryption algorithms can be written, and the signature provided.

Hashing Functions in Go

Hashing is the act of converting a variable-length message to a unique, fixed-length alphanumeric string. Hashes are one-way and non-invertible. Hashing is a one-way process that involves mathematical scrambling.

Hashes must be salted — adding a random string to the end of the password or hashed message.

Hashing cannot be reversed. Instead, matches are made between the original data and the hashed value. Hence, they are commonly used for password validation.

As mentioned earlier, there are two libraries for cryptographic functions in Go. The standard library crypto package is less advanced than the supplementary package. The following are a mix of available hashing packages in Go and how they are imported:

const (
    MD4         // import golang.org/x/crypto/md4
    MD5         // import crypto/md5
    SHA1        // import crypto/sha1
    SHA224      // import crypto/sha256
    SHA256      // import crypto/sha256
    SHA384      // import crypto/sha512
    SHA512      // import crypto/sha512
    RIPEMD160   // import golang.org/x/crypto/ripemd160
    SHA3_224    // import golang.org/x/crypto/sha3
    SHA3_256    // import golang.org/x/crypto/sha3
    SHA3_384    // import golang.org/x/crypto/sha3
    SHA3_512    // import golang.org/x/crypto/sha3
    SHA512_224  // import crypto/sha512
    SHA512_256  // import crypto/sha512
    BLAKE2s_256 // import golang.org/x/crypto/blake2s
    BLAKE2b_256 // import golang.org/x/crypto/blake2b
    BLAKE2b_384 // import golang.org/x/crypto/blake2b
    BLAKE2b_512 // import golang.org/x/crypto/blake2b
)

Let’s look at how to hash small files with the sha256 algorithm. Create a file named hash_function.go and input this code:

package main

import (
    sha2562 "crypto/sha256"
    "io"
    "log"
    "os"
)

func main() {
    file, err := os.Open("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    buffer := make([]byte, 30*1024)
    sha256 := sha2562.New()
    for {
        n, err := file.Read(buffer)
        if n > 0 {
            _, err := sha256.Write(buffer[:n])
            if err != nil {
                log.Fatal()
            }
        }
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Printf("Read %d bytes: %v", n, err)
            break
        }
    }
    sum := sha256.Sum(nil)
    log.Printf("%x\n", sum)
}

In the code above, a small file named file.txt is hashed. Replace the sha256 above with any other method if you want to use another hashing function.

Run go run hash_function.go to see the hash value:

2022/04/03 10:45:36 139524da02594b72fcb120da00fba02edda7b28c12d74d63b4b88e161235f994

Let’s see something important. Make a very small change to the file.txt and rerun the file.

2022/04/03 10:48:32 f8c903bbb068c3426a96151dfb253beb3d354906fefee6557335c9262862362a

You notice a huge and unrelated change in the hash value. This is important in Blockchain.

Conclusion

You have learned about the Go standard library support for Cryptographic functions as well as the different types of cryptographic functions that are mostly adopted.

Whether you want to use cryptography as a web developer, security engineer, or blockchain engineer, this article provides a solid foundation for the most used algorithms.