Golang and Cryptography

I teach mainly using Python, as it’s a great language to learn principles. But I develop most of my current code in Rust and Golang, as I…

Golang and Cryptography

I teach mainly using Python, as it’s a great language to learn principles. But I develop most of my current code in Rust and Golang, as I often need to access specialised cryptography, and which needs to run fast and securely. The thing I love about cryptography is that it forces me to learn Python, Golang, Java, C#, Node.js, and Rust, and never settle for just one of them.

So, for my 1,300th blog post, let’s have a look at some basics involved in cryptography using Golang [slides]. If you know some cryptography, hopefully you will learn a bit of Golang, and vice-versa. Here’s a presentation:

Formats

The most important representations for data in encryption are: hexadecimal and Base-64. For this, we can convert from a plaintext format into a binary form, and then use hexademical and Base-64 to represent these values:

In cryptography we often deal with byte array, either of a fixed length or which are variable in length. A common format in displaying binary or byte information is hexadecimal:

and in Base64:

The following implements some hex and Base64 conversions [here]:

package main
import (
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"os"
)
func main() {
s := "hello"
    argCount := len(os.Args[1:])
    if argCount > 0 {
s = os.Args[1]
}
    hash := md5.Sum([]byte(s))
hash2 := sha256.Sum256([]byte(s))
    //h1.Write([]byte(s))
    fmt.Printf("Input:\t\t\t%s\n", s)
fmt.Printf("Input (hex):\t\t%x\n", []byte(s))
fmt.Printf("Input (hex):\t\t%s\n", hex.EncodeToString([]byte(s)))
fmt.Printf("Input (Base64):\t\t%s\n", base64.StdEncoding.EncodeToString([]byte(s)))
fmt.Printf("MD5 (Hex):\t\t%x\n", hash)
fmt.Printf("MD5 (hex):\t\t%s\n", hex.EncodeToString(hash[:]))
fmt.Printf("MD5 (Base64):\t\t%s\n", base64.StdEncoding.EncodeToString(hash[:]))
fmt.Printf("SHA-256 (Hex):\t\t%x\n", hash2)
fmt.Printf("SHA-256 (hex):\t\t%s\n", hex.EncodeToString(hash2[:]))
fmt.Printf("SHA-256 (Base64):\t%s\n", base64.StdEncoding.EncodeToString(hash2[:]))
}

And a sample run:

Input:                            hello
Input (hex): 68656c6c6f
Input (hex): 68656c6c6f
Input (Base64): aGVsbG8=
MD5 (Hex): 5d41402abc4b2a76b9719d911017c592
MD5 (hex): 5d41402abc4b2a76b9719d911017c592
MD5 (Base64): XUFAKrxLKna5cZ2REBfFkg==
SHA-256 (Hex): 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256 (hex): 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256 (Base64): LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=

For Big Integers, in cryptography we often need integer values which are greater than 32-bit or 64-bit values:

The following shows some of the operations that we can perform on Big Integers [here]. The core methods that we often use are Add(), Sub(), Mul(), Mod() and Exp(). In cryptography, we often use the inverse mod function — ModInverse(). Notice in the following the Exp() is performed with respect to a (mod p) operation:

package main
import (
"fmt"
"math/big"
"os"
"strings"
)
func isHexString(s string) bool {
return strings.ContainsAny(s, "abc")
}
func toBigInt(s string) *big.Int {
x, _ := new(big.Int).SetString("0", 16)
	if strings.HasPrefix(s, "0x") {
s = strings.Replace(s, "0x", "", 1)
x, _ = new(big.Int).SetString(s, 16)
} else if strings.HasPrefix(s, "0") {
x, _ = new(big.Int).SetString(s, 8)
} else if isHexString(s) {
x, _ = new(big.Int).SetString(s, 16)
	} else {
x, _ = new(big.Int).SetString(s, 10)
	}
return (x)
}
func main() {
	v1 := "2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb6"
v2 := "2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb4"
p1 := "127"
argCount := len(os.Args[1:])
//	x2, _ := new(big.Int).SetString(v1, 16)
	if argCount > 0 {
v1 = os.Args[1]
}
if argCount > 1 {
v2 = os.Args[2]
}
if argCount > 2 {
p1 = os.Args[3]
}

	x1 := toBigInt(v1)
x2 := toBigInt(v2)
prime := toBigInt(p1)
fmt.Printf("Value 1 in hex: %x\n", v1)
fmt.Printf("Value 1 in decimal: %s\n", v1)
fmt.Printf("Value 2 in hex: %x\n", v1)
fmt.Printf("Value 2 in decimal: %s\n", v1)
	fmt.Printf("v1+v2= %s\n", new(big.Int).Add(x1, x2).String())
fmt.Printf("v1-v2= %s\n", new(big.Int).Sub(x1, x2).String())
fmt.Printf("v1*v2= %s\n", new(big.Int).Mul(x1, x2).String())

	fmt.Printf("p== %s\n", prime)
p := new(big.Int).Exp(x1, x2, prime) // x1^x2 (mod prime)
fmt.Printf("v1^v2 (mod p)= %s\n", prime)
	p = new(big.Int).Mul(x1, x2)
p = new(big.Int).Mod(p, prime)
fmt.Printf("v1*v2 (mod p)= %s\n", p)
	p = new(big.Int).Add(x1, x2)
p = new(big.Int).Mod(p, prime)
fmt.Printf("v1+v2 (mod p)= %s\n", p)
	Invx2 := new(big.Int).ModInverse(x2, prime) // x2^{-1} (mod prime)
p = new(big.Int).Mul(x1, Invx2)
p = new(big.Int).Mod(p, prime)
fmt.Printf("v1/v2 (mod p)= %s\n", p)
}

A sample run is:

Value 1 in hex: 32653633643233396233666334373433336631393738393834336533303531346235336466643837373365626339313563306263373734653533363864626236
Value 1 in decimal: 2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb6
Value 2 in hex: 32653633643233396233666334373433336631393738393834336533303531346235336466643837373365626339313563306263373734653533363864626236
Value 2 in decimal: 2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb6
v1+v2= 41965519633295562940561227248429783749428889582066031303567839694283936479082
v1-v2= 2
v1*v2= 440276209523128839864726536429235629067156961787158330200549510715779581777397206383182995070835335229221800721907970830304626962641835121492370755890680
p== 127
v1^v2 (mod p)= 127
v1*v2 (mod p)= 21
v1+v2 (mod p)= 56
v1/v2 (mod p)= 34

Symmetric key

The workhorse of cryptography is symmetric key encryption, and where Bob and Alice use the same key to encrypt and decrypt:

With a block cipher, we split up our data into blocks and then need to add padding at the end, as we are unlikely to fill up all the blocks:

Here is a program to add and remove the most common format of padding (PKCS7):

package main
import (
"fmt"
"os"
"strconv"
	"github.com/enceve/crypto/pad"
)
func main() {
	msg := "Hello1111111111111"
argCount := len(os.Args[1:])
blocksize := 16
	if argCount > 0 {
msg = string(os.Args[1])
}
if argCount > 1 {
blocksize, _ = strconv.Atoi(os.Args[2])
}
	m := []byte(msg)
fmt.Printf("Block size:\t%d bytes (%d bits)\n\n", blocksize, blocksize*8)
	pkcs7 := pad.NewPKCS7(blocksize)
pad1 := pkcs7.Pad(m)
fmt.Printf("PKCS7 Pad:\t%x", pad1)
res, _ := pkcs7.Unpad(pad1)
fmt.Printf("\n Unpad:\t%s", res)
	x923 := pad.NewX923(blocksize)
pad2 := x923.Pad(m)
fmt.Printf("\n\nX923 Pad:\t%x", pad2)
res, _ = x923.Unpad(pad2)
fmt.Printf("\n Unpad:\t%s", res)
	ISO10126 := pad.NewISO10126(blocksize, nil)
pad3 := ISO10126.Pad(m)
fmt.Printf("\n\nXISO1012 Pad:\t%x", pad3)
res, _ = ISO10126.Unpad(pad3)
fmt.Printf("\n Unpad:\t%s", res)
}

A sample run is:

Block size:     16 bytes (128 bits)
PKCS7 Pad:      48656c6c6f0b0b0b0b0b0b0b0b0b0b0b
Unpad: Hello
X923 Pad:       48656c6c6f000000000000000000000b
Unpad: Hello
XISO1012 Pad:   48656c6c6f414a74068d99d58027020b
Unpad: Hello

For AES encryption we see three main modes: block; stream and AEAD. For a stream cipher we generate a pseudo infinite keystream from a secret key and a salt value and then XOR the plaintext stream and the keystream:

For ECB, we do not use a salt value:

With CBC, we have salt value, and chain each of the blocks:

In AEAD, we add in some additional data to bind the ciphertext:

The following defines an implementation of block, AEAD and stream encryption for AES symmetric key:

package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"os"
"strconv"
"strings"
	"github.com/enceve/crypto/pad"
"golang.org/x/crypto/pbkdf2"
)
func getSalt(n int) []byte {
nonce := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
return (nonce)
}
func main() {
	msg := "hello"
passwd := "qwerty"
mode := "gcm"
size := 16
	argCount := len(os.Args[1:])
if argCount > 0 {
msg = os.Args[1]
}
if argCount > 1 {
passwd = os.Args[2]
}
if argCount > 2 {
mode = os.Args[3]
}
if argCount > 3 {
size, _ = strconv.Atoi(os.Args[4])
}
	pwsalt := getSalt(12) // 96 bits for nonce/IV
key := pbkdf2.Key([]byte(passwd), pwsalt, 10000, size, sha256.New)
block, _ := aes.NewCipher(key)
	var salt []byte
var plain []byte
var ciphertext []byte
	plaintext := []byte(msg)
	if mode == "gcm" {
// AEAD
salt = getSalt(12)
aesgcm, _ := cipher.NewGCM(block)
ciphertext = aesgcm.Seal(nil, salt, plaintext, nil)
plain, _ = aesgcm.Open(nil, salt, ciphertext, nil)
} else if mode == "cbc" {
// Block cipher
plain = make([]byte, (len(plaintext)/16+1)*aes.BlockSize)
ciphertext = make([]byte, (len(plaintext)/16+1)*aes.BlockSize)
salt = getSalt(16)
pkcs7 := pad.NewPKCS7(aes.BlockSize)
pad1 := pkcs7.Pad(plaintext)
blk := cipher.NewCBCEncrypter(block, salt)
blk.CryptBlocks(ciphertext, pad1)
blk = cipher.NewCBCDecrypter(block, salt)
blk.CryptBlocks(plain, ciphertext)
plain, _ = pkcs7.Unpad(plain)
	} else if mode == "cfb" {
// Stream cipher
salt = getSalt(aes.BlockSize)
plain = make([]byte, len(plaintext))
ciphertext = make([]byte, len(plaintext))
		stream := cipher.NewCFBEncrypter(block, salt)
stream.XORKeyStream(ciphertext, plaintext)
stream = cipher.NewCFBDecrypter(block, salt)
stream.XORKeyStream(plain, ciphertext)
}
	fmt.Printf("Mode:\t\t%s\n", strings.ToUpper(mode))
fmt.Printf("Key size:\t%d bits\n", size*8)
fmt.Printf("Message:\t%s\n", msg)
	fmt.Printf("Password:\t%s\n", passwd)
fmt.Printf("Password Salt:\t%x\n", pwsalt)
fmt.Printf("\nKey:\t\t%x\n", key)
fmt.Printf("\nCipher:\t\t%x\n", ciphertext)
	fmt.Printf("Salt:\t\t%x\n", salt)
fmt.Printf("\nDecrypted:\t%s\n", plain)
}

A sample run for GCM is:

Mode:           GCM
Key size: 128 bits
Message: hello
Password: qwerty
Password Salt: 0c7708fead0911c4c83319f9
Key:            2af2a2a1b85432856be7887de01217f9
Cipher:         9e520dfde0b2f615e62a52ad306b3f208959e1a97d
Salt: 6e21bc49b60c72dc93b027a1
Decrypted:      hello

Hashing

With hashing we create a fixed-length binary output which provides a one-way function:

For MD5 we have a 128-bit hash (and which is seen to be insecure, as we can easily create a hash collision):

For SHA-1 we increase the hash size to 160-bits, and which is more secure, but still is defined as a security risk:

Some of the key hashing methods are:

The following is some code:

package main
import (
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
	"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/sha3"
)
type params struct {
memory uint32
iterations uint32
parallelism uint8
saltLength uint32
keyLength uint32
}
func generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
	return b, nil
}
func main() {
s := "Abc"
	h1 := md4.New()
h2 := md5.Sum([]byte(s))
h3 := sha1.Sum([]byte(s))
h4 := sha256.Sum256([]byte(s))
h5 := sha512.Sum512([]byte(s))
h6 := ripemd160.New()
h7 := sha3.Sum256([]byte(s))
h8 := sha3.Sum512([]byte(s))
h9 := blake2b.Sum256([]byte(s))
h10 := blake2b.Sum512([]byte(s))
	p := ¶ms{
memory: 64 * 1024,
iterations: 1,
parallelism: 1,
saltLength: 16,
keyLength: 32,
}
	salt, err := generateRandomBytes(p.saltLength)
h11 := argon2.IDKey([]byte(s), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
	// Values are password, salt, N, r, p, key length
h12, err := scrypt.Key([]byte(s), salt, 1 <<15, 4, 1, 32)
cost := 1
h13, err := bcrypt.GenerateFromPassword([]byte(s), cost)
	h1.Write([]byte(s))
h6.Write([]byte(s))
fmt.Println("Input: ", s)
fmt.Printf(" MD4: %x\n", h1.Sum(nil))
fmt.Printf("MD5: %x\n", h2)
fmt.Printf("SHA-1: %x\n", h3)
fmt.Printf("SHA-256: %x\n", h4)
fmt.Printf("SHA-512: %x\n", h5)
fmt.Printf("RIPEMD160: %x\n", h6.Sum(nil))
fmt.Printf("SHA-3 256: %x\n", h7)
fmt.Printf("SHA-3 512: %x\n", h8)
fmt.Printf("Blake2b 256-bit: %x\n", h9)
fmt.Printf("Blake2b 512-bit: %x\n", h10)
if err == nil {
fmt.Printf("Argon2: %x\n", h11)
fmt.Printf("Scrypt: %x\n", h12)
fmt.Printf("BCrypt: %s\n", h13)
}
}

A sample run:

Input:  Abc
MD4: d4ce244fc98785d1eafd751b3b00ac00
MD5: 35593b7ce5020eae3ca68fd5b6f3e031
SHA-1: 915858afa2278f25527f192038108346164b47f2
SHA-256: 06d90109c8cce34ec0c776950465421e176f08b831a938b3c6e76cb7bee8790b
SHA-512: 047b10fe577a23efd96546dcfce8485fc4aa8ae84dd3bf0c435a294cf318c7a260418dd96a97feb0ad7aed90ff011620cfa5d7b3cdc8aea4c4e81e56a0fc9934
RIPEMD160: f6f268b47b3d984c3efad5a36cb1890c5cf0f839
SHA-3 256: 6f7d689169ef1894c906a7fd1bbf91912173277ee992b666658118a67b054fbb
SHA-3 512: 207fd38cf640abb5d5740f774cd82f69dbecc66f3715727d674ea2e9f0a5d37314525f1e747493b382acf2516d0a4b4513e00a0217ec98b2c4614ad2bb4aa9c1
Blake2b 256-bit: f7641b2e22df1dd8d93fbdd76cb12e748b3ca0025224a06ebf4c5a88bafbd5ad
Blake2b 512-bit: 2916519a16d56ffc0ab56cc3c8ab3e8c0b06d53c71002edbc740066d64190d3ef1171c54253c7668e7a6f79eabcf726de5e836463be79fbec352b332397be1e7
Argon2: 44a060840466c3ad5c3d4fc0748c97c0696ab7549d06c85c65a7f6f74541215e
Scrypt: 86b93fce7cdbbf3e20564fe8f3bb5549db7841c6290a0c461e1984ab5c48798a
BCrypt: $2a$10$4iyk2eNPhCBJgV0fSHBjd.6YljK5xJJG5csXgFuq9IuH3I16glkEK

HMAC

With HMAC we can create an authenticated hash using a shared secret key. Bob and Alice will share the same secret key:

The following is some code to implement HMAC-SHA256 and HMAC-SHA512:

package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"io"
"os"
)
func getSalt(n int) []byte {
nonce := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
return (nonce)
}
func main() {
key:="000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"
m := "The quick brown fox jumps over the lazy dog"
	argCount := len(os.Args[1:])
	if argCount > 0 {
m = os.Args[1]
}
	if argCount > 1 {
key = os.Args[2]
}
	var secretKey, _ = hex.DecodeString(key)
	message := []byte(m)
// salt := getSalt(16)
	fmt.Printf("Message: %s\n", m)
fmt.Printf("Key: %x\n", secretKey)
// fmt.Printf("\nSalt: %x\n", salt)
	hash := hmac.New(sha256.New, []byte(secretKey))
hash.Write(message)
// hash.Write(salt)
fmt.Printf("\nHMAC-Sha256: %x", hash.Sum(nil))
	hash = hmac.New(sha512.New, []byte(secretKey))
hash.Write(message)
// hash.Write(salt)
fmt.Printf("\n\nHMAC-sha512: %x", hash.Sum(nil))
}

A sample run:

Message: The quick brown fox jumps over the lazy dog
Key: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
HMAC-Sha256: f87ad256151fc7b4c5dffa4adb3ebe911a8eeb8a8ebdee3c2a4a8e5f5ec02c32
HMAC-sha512: 0623d51f882717efa360aa2217d0b554b57ea018eb518178b23045941a6ae24450af5c980f6ebca94ca5314a8590991b4eab6daa3f0c109345433f44ee234d00

For HKDF (HMAC Key Derivation Function) we can generate a shared key based on a secret and salt value:

Using HKDF is the proper way to create an encryption key from a secret value and a salt value. The secret could be a password, or some random data. Some code is:

package main
import (
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"os"
	"golang.org/x/crypto/hkdf"
)
func getSalt(n int) []byte {
nonce := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
return (nonce)
}
func main() {
hash := sha256.New
s := "The quick brown fox jumps over the lazy dog."
salt := getSalt(hash().Size())
	info := []byte("")
	argCount := len(os.Args[1:])
	if argCount > 0 {
s = os.Args[1]
}
secret := []byte(s)
	kdf := hkdf.New(hash, secret, salt, info)
	key1 := make([]byte, 16)
_, _ = io.ReadFull(kdf, key1)
	fmt.Printf("Secret: %s\n", s)
fmt.Printf("Salt: %x\n", salt)
fmt.Printf("\nHKDF 16 byte key: %x\n", key1)
	key2 := make([]byte, 32)
_, _ = io.ReadFull(kdf, key2)
	fmt.Printf("HKDF 32 byte key: %x\n", key2)
}

A sample run is:

Secret: The quick brown fox jumps over the lazy dog.
Salt: 282d76707f86b48ae91357b3070a0869f7c9ee53e105fdfbb5f51c83b9c21a87
HKDF 16 byte key: 327ee9b0b46815b85260be49f125a985
HKDF 32 byte key: 7e41b34d7ae05a44f13bbf3885ab2f517888e4819b752802d65bf8e6eea0b43b

Public key

With public-key encryption, we create a public and a private key.

and the main methods are: RSA; ElGamal; and ECC:

The following is an implementation of RSA:

package main
import (
"crypto/rand"
"fmt"
"math/big"
"os"
"strconv"
)
func main() {
	psize := 128
e := new(big.Int).SetInt64(65537)
Mval := "12"
	argCount := len(os.Args[1:])
	if argCount > 0 {
Mval = os.Args[1]
}
if argCount > 1 {
psize, _ = strconv.Atoi(os.Args[2])
}
	p, _ := rand.Prime(rand.Reader, psize)
q, _ := rand.Prime(rand.Reader, psize)
	M, _ := new(big.Int).SetString(Mval, 10) // Base 10
	N := new(big.Int).Mul(p, q)
	C := new(big.Int).Exp(M, e, N)
	Pminus1 := new(big.Int).Sub(p, new(big.Int).SetInt64(1))
Qminus1 := new(big.Int).Sub(q, new(big.Int).SetInt64(1))
PHI := new(big.Int).Mul(Pminus1, Qminus1)
	d := new(big.Int).ModInverse(e, PHI)
	Plain := new(big.Int).Exp(C, d, N)
	fmt.Printf("M= %s\n", M)
fmt.Printf("p= %s\n", p)
fmt.Printf("q= %s\n", q)
	fmt.Printf("N= %s\n", N)
fmt.Printf("Public: e= %s\n", e)
fmt.Printf("Private: d= %s\n", d)
fmt.Printf("\nCipher C= %s\n", C)
fmt.Printf("\nDecrypt Plain= %s\n", Plain)
}

A sample run is:

M= 12
p= 295806851467514971672292028961750769609
q= 269393212158044082406569589759963515197
N= 79688357895191294286647587852540487066377756432903938914060265832475717247973
Public: e= 65537
Private: d= 64734859483682336947477469668758151744766997093916591625578680423822411214369
Cipher C= 10608469162454498211673212126644030923108520854821147642475399122477145638162
Decrypt Plain= 12

For ElGamal, we use discrete logarithms:

package main
import (
"crypto/rand"
"fmt"
"io"
"math/big"
)
func get_g_p(size string) (g, p *big.Int) {
	if size == "512" {
p, _ = new(big.Int).SetString("FCA682CE8E12CABA26EFCCF7110E526DB078B05EDECBCD1EB4A208F3AE1617AE01F35B91A47E6DF63413C5E12ED0899BCD132ACD50D99151BDC43EE737592E17", 16)
		g, _ = new(big.Int).SetString("678471B27A9CF44EE91A49C5147DB1A9AAF244F05A434D6486931D2D14271B9E35030B71FD73DA179069B32E2935630E1C2062354D0DA20A6C416E50BE794CA4", 16)
	}
if size == "768" {
p, _ = new(big.Int).SetString("E9E642599D355F37C97FFD3567120B8E25C9CD43E927B3A9670FBEC5D890141922D2C3B3AD2480093799869D1E846AAB49FAB0AD26D2CE6A22219D470BCE7D777D4A21FBE9C270B57F607002F3CEF8393694CF45EE3688C11A8C56AB127A3DAF", 16)
		g, _ = new(big.Int).SetString("30470AD5A005FB14CE2D9DCD87E38BC7D1B1C5FACBAECBE95F190AA7A31D23C4DBBCBE06174544401A5B2C020965D8C2BD2171D3668445771F74BA084D2029D83C1C158547F3A9F1A2715BE23D51AE4D3E5A1F6A7064F316933A346D3F529252", 16)
	}
if size == "1024" {
p, _ = new(big.Int).SetString("FD7F53811D75122952DF4A9C2EECE4E7F611B7523CEF4400C31E3F80B6512669455D402251FB593D8D58FABFC5F5BA30F6CB9B556CD7813B801D346FF26660B76B9950A5A49F9FE8047B1022C24FBBA9D7FEB7C61BF83B57E7C6A8A6150F04FB83F6D3C51EC3023554135A169132F675F3AE2B61D72AEFF22203199DD14801C7", 16)
		g, _ = new(big.Int).SetString("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A", 16)
	}
return
}
type PublicKey struct {
G, P, Y *big.Int
}
type PrivateKey struct {
PublicKey
X *big.Int
}
func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (a, b *big.Int, err error) {
	k, _ := rand.Int(random, pub.P)
	m := new(big.Int).SetBytes(msg)
	a = new(big.Int).Exp(pub.G, k, pub.P)
s := new(big.Int).Exp(pub.Y, k, pub.P)
b = s.Mul(s, m)
b.Mod(b, pub.P)
	return
}
func Decrypt(priv *PrivateKey, a, b *big.Int) (msg []byte, err error) {
s := new(big.Int).Exp(a, priv.X, priv.P)
s.ModInverse(s, priv.P)
s.Mul(s, b)
s.Mod(s, priv.P)
em := s.Bytes()
	return em, nil
}
func main() {
	s := "hello world"
x := "40"
plen := "512"
	//    s = string(os.Args[1])
// x = string(os.Args[2])
// plen =string(os.Args[3])
	fmt.Printf("Input message: %s\n", s)
fmt.Printf("Prime number size: %s\n\n", plen)
	xval, _ := new(big.Int).SetString(x, 10)
	g, p := get_g_p(plen)
	priv := &PrivateKey{
PublicKey: PublicKey{
G: g,
P: p,
},
X: xval,
}
priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P)
	message := []byte(s)
a, b, _ := Encrypt(rand.Reader, &priv.PublicKey, message)
	message2, _ := Decrypt(priv, a, b)
	fmt.Printf("====Private key (x):\nX=%d", priv.X)
fmt.Printf("\n\n====Public key (Y,G,P):\nY=%d\n\nG=%d\n\nP=%d", priv.Y, priv.PublicKey.G, priv.PublicKey.P)
	fmt.Printf("\n\n====Cipher (a=%s)\n\n(b=%s): ", a, b)
fmt.Printf("\n\n====Decrypted: %s", string(message2))
}

A sample run is:

Input message: hello world
Prime number size: 512
====Private key (x):
X=40
====Public key (Y,G,P):
Y=11765933737659181064003945366380762137134618693834882798460832005540886987522854475566040189884295346667906296155495198252840497522102322260522882578915137
G=5421644057436475141609648488325705128047428394380474376834667300766108262613900542681289080713724597310673074119355136085795982097390670890367185141189796
P=13232376895198612407547930718267435757728527029623408872245156039757713029036368719146452186041204237350521785240337048752071462798273003935646236777459223
====Cipher (a=159783758277664012928674851197220532735544644017547303825497596160008339162415680504589377836091871017589903008929153269423823510319876676018134081389599)
(b=1565286765679818277375001876954396058169539985188749813327442938468495299977799355944246916699970457317552490495405386419565432882567243815601446053149268): 
====Decrypted: hello world

ECDSA and EdDSA

For digital signatures, we encrypt a hash of a message with a private key, and then prove this signature with the public key:

For ECDA — as used with Bitcoin and Ethereum — we create a random nonce (k), and then use a private key and a hash of the message to create a digital signature (r,s):

Some code to implement:

package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"os"
"strings"
	"github.com/dustinxie/ecc"
)
func getCurve(s string) elliptic.Curve {
if strings.Contains(s, "384") {
return (ecc.P384())
} else if strings.Contains(s, "521") {
return (ecc.P521())
}
return (ecc.P256k1())
}
func main() {
	msg := "Hello 123"
curveType := ""
	argCount := len(os.Args[1:])
if argCount > 0 {
msg = os.Args[1]
}
if argCount > 1 {
curveType = os.Args[2]
}
	pubkeyCurve := getCurve(curveType)
m := []byte(msg)
digest := sha256.Sum256(m)
	privatekey, _ := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
	pubkey := privatekey.PublicKey
	r, s, _ := ecdsa.Sign(rand.Reader, privatekey, digest[:])
	fmt.Printf("=== Message ===\n")
fmt.Printf("Msg=%s\nHash=%x\n", msg, digest)
fmt.Printf("\n=== Private key ===\n")
fmt.Printf("Private key=%x\n", privatekey.D)
fmt.Printf("Curve=%s\n", privatekey.Curve.Params().Name)
	fmt.Printf("\n=== Public key (X,Y) ===\n")
fmt.Printf("X=%s Y=%s\n", pubkey.X, pubkey.Y)
fmt.Printf(" Hex: X=%x Y=%x\n", pubkey.X.Bytes(), pubkey.Y.Bytes())
fmt.Printf("\n=== Signature (R,S) ===\n")
fmt.Printf("R=%s S=%s\n", r, s)
fmt.Printf(" Hex: R=%x S=%x\n", r, s)
	rtn := ecdsa.Verify(&pubkey, digest[:], r, s)
	if rtn {
fmt.Printf("Signature verifies")
} else {
fmt.Printf("Signature does not verify")
}
}

A sample run is:

=== Private key ===
Private key=d2a60621f5866bd413c8e98fa90c0ea696b99bb3dee9262c65a3a540ead5d46a
Curve=P-256k1
=== Public key (X,Y) ===
X=80821331051023746223830068219547774778570362684127746989627081930841526444419 Y=58249137465050673556703463636023643181757172398528046136871137906486743981588
Hex: X=b2af40966979d9d2d4d388f476c2a790236997253ac067145d655a6fdd612183 Y=80c7d7ed9376bfae0ccd8ece6f5f812c08d79d63df60135d6cfbcb9dcad4de14
=== Signature (R,S) ===
R=73267642912309299955050307148272353870495402285772390441578067564792986384508 S=50408473822392060484338227503577083148905530272862983096693552784217265409905
Hex: R=a1fc042d5df880ce79483c2c52d60c46b13f314bba4f8e20277b8d6ad4aeb87c S=6f722f637ce219a0c13dec485ab048b8387806bc95e5b76773d5738b20400f71
Signature verifies

The following verifies the signature:

package main
import (
"crypto/ecdsa"
"crypto/sha256"
"fmt"
"math/big"
"os"
"strings"
	"github.com/dustinxie/ecc"
)
func isHexString(s string) bool {
return strings.ContainsAny(s, "abcdef")
}
func toBigInt(s string) *big.Int {
x, _ := new(big.Int).SetString("0", 16)
if isHexString(s) {
x, _ = new(big.Int).SetString(s, 16)
	} else {
x, _ = new(big.Int).SetString(s, 10)
	}
return (x)
}
func main() {
	msg := "Hello 123"
r := "2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb6"
s := "39b22fd14b77ae40399340f55fe0ceced6b3107a57bffc65c1dbc9b7a91a050a"
bobpub_X := "46cab231f27992deef926ed08b4c8102505d8a29ed0b544777a7ded3996a059c"
bobpub_Y := "cc86ca06058be27a7e61830f4e804a02ff072d59e78749b544640280aed73c53"
	argCount := len(os.Args[1:])
	if argCount > 0 {
msg = os.Args[1]
}
	if argCount > 1 {
r = os.Args[2]
}
if argCount > 2 {
s = os.Args[3]
}
	if argCount > 3 {
bobpub_X = os.Args[4]
}
if argCount > 4 {
bobpub_Y = os.Args[5]
}
	m := []byte(msg)
digest := sha256.Sum256(m)
	x := toBigInt(bobpub_X)
y := toBigInt(bobpub_Y)
R := toBigInt(r)
S := toBigInt(s)
	publicKey := ecdsa.PublicKey{
Curve: ecc.P256k1(), //secp256k1
X: x,
Y: y,
}
	fmt.Printf("Message to sign: %s\n", msg)
	fmt.Printf("Bob Public Key: X:%s Y:%s\n", bobpub_X, bobpub_Y)
	fmt.Printf("Bob Signature: R:%s S:%s\n", r, s)
	fmt.Printf("\n=== Now checking with Bob's public key ===\n")
	rtn := ecdsa.Verify(&publicKey, digest[:], R, S)
	if rtn {
fmt.Printf("Signature works. Yipee!")
} else {
fmt.Printf("Signature does not check out!!!")
}
}

A sample run:

Message to sign: Hello 123
Bob Public Key: X:46cab231f27992deef926ed08b4c8102505d8a29ed0b544777a7ded3996a059c Y:cc86ca06058be27a7e61830f4e804a02ff072d59e78749b544640280aed73c53
Bob Signature: R:2e63d239b3fc47433f19789843e30514b53dfd8773ebc915c0bc774e5368dbb6 S:39b22fd14b77ae40399340f55fe0ceced6b3107a57bffc65c1dbc9b7a91a050a
=== Now checking with Bob's public key ===
Signature works. Yipee!

For EdDSA:

Some code to implement:

package main
import (
"crypto/ed25519"
"crypto/sha256"
"fmt"
"os"
)
func main() {
	msg := "Hello 1234"
	argCount := len(os.Args[1:])
if argCount > 0 {
msg = os.Args[1]
}
	publ, priv, _ := ed25519.GenerateKey((nil))
	m := []byte(msg)
digest := sha256.Sum256(m)
	sig := ed25519.Sign(priv, digest[:])
	fmt.Printf("=== Message ===\n")
fmt.Printf("Msg=%s\nHash=%x\n", msg, digest)
fmt.Printf("\n=== Private key ===\n")
fmt.Printf("Public key=%x\n\n", publ)
fmt.Printf("Private key=%x\n\n", priv[0:32])
fmt.Printf("Signature: (%x,%x)\n\n", sig[0:32], sig[32:64])
	rtn := ed25519.Verify(publ, digest[:], sig)
	if rtn {
fmt.Printf("Signature verifies")
} else {
fmt.Printf("Signature does not verify")
}
}

A sample run:

=== Message ===
Msg=Hello 1234
Hash=798dafff617a026d7c433bee5678a89cba023eef003b5b92f9daee401d6d7f93
=== Private key ===
Public key=6a34676b06e4061e7e75cb8b1469399ac82c7a588e75c3b97f602de4d42b2f8a
Private key=3566b01b5ad4cc82635fe308563286070a052ba5a4322a85a29fe2f1b7d5939c
Signature: (1c021af5a08a82fd125e93314b5e16ab75b88229f8c65b51daf59b7254a7440d,923c7e87fbbb4cad100f62346bab83846907a81c77bbe43600ff27dcf23b2e0e)
Signature verifies

If you want to see a range of implementations, try here:

https://asecuritysite.com/golang/