Top

Cross Programming Language Encryption – CSharp vs Go, Part 2

Cross Programming Language Encryption – CSharp vs Go, Part 2

GitHub stars GitHub followers
See a demo at Github

The first part of this article, discussed Encryption coding at C# level. It introduced you in the world of AES and RSA encryption algorithms. But the role of this story is not only to introduce you to some encryption algorithms but also show you how to code them under some programming languages.

As mentioned before, our team goal was to encrypt messages from a module written in C#, and decrypt them in a module written in Go lang.

Coding AES with Go

For this purpose, I have chosen to define a structure holding the Key and the Initialization Vector (IV). As explained in the previous article, we will use a private Hash which will be shared between encryptor and decryptor, while the IV will be attached to the encrypted content.


// AESCrypt -
type AESCrypt struct {
	Key []byte
	IV  []byte
}

// NewAESCrypt -
func NewAESCrypt(Hash string) (*AESCrypt, error) {
	enc := AESCrypt{}

	if len(Hash) != 16 && len(Hash) != 24 && len(Hash) != 32 {
		return nil, fmt.Errorf("invalid hash length. must be 16, 24 or 32")
	}

	enc.Key = []byte(Hash)
	enc.IV = make([]byte, 16)
	if _, err := io.ReadFull(rand.Reader, enc.IV); err != nil {
		return nil, fmt.Errorf("unable to generate IV: %s", err.Error())
	}

	return &enc, nil
}

Next step, considering our C# experience, is picking our Block Cipher Mode. From this point of view, Go’s crypt/cipher library contains a different list of ciphers (CBC, CFB, CTR, GCM, OFB), unlike C# (which contains CBC, CFB, CTS, ECB, OFB), but, as you can see, a few of them are common ground.

Also, considering I picked a cipher that requires no padding, you’ll see that, in Go, we won’t have to mention the idea of padding.

// Encrypt will encrypt a string password using AES algorithm, returning a Base64 for of the encrypt result
func (enc AESCrypt) Encrypt(password string) (string, error) {
	block, err := aes.NewCipher(enc.Key)
	if err != nil {
		return "", err
	}

	stream := cipher.NewCFBEncrypter(block, enc.IV)

	encPassword := make([]byte, len([]byte(password)))

	stream.XORKeyStream(encPassword, []byte(password))

	return base64.StdEncoding.EncodeToString(append(enc.IV, encPassword...)), nil
}

For the decryption method, all has been said in the C# section of this article, only mention again, being that it will be a mirror function of the encrypt one.

// Decrypt will decrypt a string password using AES algorithm, expecting a Base64 form of the encrypted password
func (enc AESCrypt) Decrypt(password string) (string, error) {

	encPassword, err := base64.StdEncoding.DecodeString(password)
	if err != nil {
		return "", err
	}

	block, err := aes.NewCipher(enc.Key)
	if err != nil {
		return "", err
	}

	enc.IV = encPassword[0:16]

	stream := cipher.NewCFBDecrypter(block, enc.IV)

	encPassword = encPassword[16:]

	decPassword := make([]byte, len(encPassword))

	stream.XORKeyStream(decPassword, encPassword)

	return string(decPassword), nil
}

Coding RSA with Go

The form of my RSA struct in Go, is not far from the AES one, the only difference being that we will need two variables, one for the public key, and the second for the private key.

// RsaCrypt -
type RsaCrypt struct {
	PubKey  *rsa.PublicKey
	PrivKey *rsa.PrivateKey
}

// NewSSLCrypt -
func NewRSACrypt(PubKeyPath string, PrivKeyPath string) (*RsaCrypt, error) {
	encTool := &SslCrypt{}

	if err := encTool.ReadPublicKey(PubKeyPath); err != nil {
		return nil, err
	}
	if err := encTool.ReadPrivateKey(PrivKeyPath); err != nil {
		return nil, err
	}

	return encTool, nil
}

Then, for the purpose of writing beautiful code, we would have to write methods to read our public and private key, since the way things work in go are not that “beautiful” as they are in C#.

// ReadPublicKey -
func (enc *RsaCrypt) ReadPublicKey(path string) error {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return fmt.Errorf("could not read public key file: %s", err.Error())
	}

	block, _ := pem.Decode(data)
	if block.Type != "CERTIFICATE" {
		return fmt.Errorf("invalid block type: %s", block.Type)
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return err
	}

	enc.PubKey = cert.PublicKey.(*rsa.PublicKey)

	return nil
}

// ReadPrivateKey -
func (enc *RsaCrypt) ReadPrivateKey(path string) error {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return fmt.Errorf("could not read private key file: %s", err.Error())
	}

	block, _ := pem.Decode(data)
	if block.Type != "PRIVATE KEY" {
		return fmt.Errorf("invalid block type: %s", block.Type)
	}

	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
		enc.PrivKey = key
		return nil
	}

	if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
		switch key := key.(type) {
		case *rsa.PrivateKey:
			enc.PrivKey = key
			return nil
		case *ecdsa.PrivateKey:
			return fmt.Errorf("found ecdsa private key type in PKCS#8 wrapping; aiming for rsa")
		default:
			return fmt.Errorf("found unknown private key type in PKCS#8 wrapping")
		}
	}

	if _, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
		// enc.PrivKey = key
		// return nil
		return fmt.Errorf("found ecdsa private key type in PKCS#8 wrapping; aiming for rsa")
	}

	return fmt.Errorf("failed to parse private key")
}

And then writing the encrypt and decrypt method.

What I did not mention in the C# part of this article, is that RSA uses a padding scheme also, and, as far as I identified them we can use two types: OAEP or PKCS1.

Again you will see that our encryption result will be encoded using Base64, for the simple reason of preventing data loss because of possible different encoding issues present on different operating systems.

// Encrypt will encrypt a string password using an RSA certificate,
// returning a Base64 for of the encrypt result
func (enc *RsaCrypt) Encrypt(password string) (string, error) {
	hash := sha512.New()

	ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, enc.PubKey, []byte(password), nil)
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt will decrypt a string password using an RSA certificate,
// expecting a Base64 form of the encrypted password
func (enc *RsaCrypt) Decrypt(password string) (string, error) {
	hash := sha512.New()

	bytes, err := base64.StdEncoding.DecodeString(password)
	if err != nil {
		return "", err
	}

	ciphertext, err := rsa.DecryptOAEP(hash, rand.Reader, enc.PrivKey, bytes, nil)
	if err != nil {
		return "", err
	}

	return string(ciphertext), nil
}

References

Related Articles

Cirjan Dragos
No Comments

Post a Comment