DEV Community

Cover image for Mastering Cryptography in Go: Introducing `go-think/openssl`
Lee
Lee

Posted on

Mastering Cryptography in Go: Introducing `go-think/openssl`

Zero-Boilerplate, 100% Cross-Language Compatible Encryption for Go Developers


Cryptography in Go has always been a tale of two worlds.

On one hand, Go's standard library crypto/* packages are well-crafted, secure, and fast. On the other hand, implementing everyday cryptographic workflows—like encrypting a payload with AES-CBC (PKCS#7), performing AES-ECB for legacy systems, or handling RSA encryption for large payloads—requires writing dozens of lines of repetitive boilerplate code.

Even worse, subtle differences in padding schemes, IV handlings, or key truncations often lead to frustrating cross-language decryption failures when communicating with PHP (openssl_encrypt), Java (javax.crypto), Python (pycryptodome), or Node.js.

To solve these headaches once and for all, meet go-think/openssl — a developer-friendly, pure Go cryptographic toolkit that brings simplicity and seamless cross-platform interoperability to Go applications.


💡 Why go-think/openssl?

  1. Pure Go, No CGO Dependencies: Lightweight, cross-compilation friendly, and built on top of standard Go crypto primitives.
  2. Zero-Boilerplate API: Do what used to take 40 lines in just 2 or 3 lines.
  3. 100% Cross-Language Compatibility: Produces identical binary ciphertext to OpenSSL, PHP, Java, Python, and Node.js.
  4. Smart Padding & Chunking:
    • Native support for PKCS#7 (PKCS#5) and ZeroPadding.
    • Automatic chunking for RSA encryption and decryption of arbitrarily long messages.
  5. Batteries Included: AES, DES, 3DES, RSA, MD5, SHA-1 to SHA-512, and HMAC.

🚀 Quick Installation

go get -u github.com/go-think/openssl
Enter fullscreen mode Exit fullscreen mode

🛠️ Code in Action

1. Symmetric Encryption: AES (CBC, ECB & GCM)

Standard Go requires manual block padding, block mode initialization, and slice slicing. With go-think/openssl, it is as simple as:

AES-CBC with PKCS#7 Padding

package main

import (
    "encoding/base64"
    "fmt"
    "github.com/go-think/openssl"
)

func main() {
    src := []byte("Hello, Cross-Language World!")
    key := []byte("1234567890123456") // 16-byte key for AES-128
    iv  := []byte("1234567890123456") // 16-byte IV

    // Encrypt
    cipherText, err := openssl.AesCBCEncrypt(src, key, iv, openssl.PKCS7_PADDING)
    if err != nil {
        panic(err)
    }
    fmt.Println("Base64 Ciphertext:", base64.StdEncoding.EncodeToString(cipherText))

    // Decrypt
    plainText, err := openssl.AesCBCDecrypt(cipherText, key, iv, openssl.PKCS7_PADDING)
    if err != nil {
        panic(err)
    }
    fmt.Println("Decrypted:", string(plainText))
}
Enter fullscreen mode Exit fullscreen mode

Modern AEAD: AES-GCM (Authenticated Encryption)

For applications that require authenticated encryption with integrity checks:

nonce := []byte("123456789012")          // 12-byte standard nonce
aad   := []byte("authenticated_header") // Additional authenticated data

// Encrypt
dst, err := openssl.AesGCMEncrypt(src, key, nonce, aad)

// Decrypt
plain, err := openssl.AesGCMDecrypt(dst, key, nonce, aad)
Enter fullscreen mode Exit fullscreen mode

Also supports CFB, OFB, CTR, as well as legacy DES and 3DES ciphers out of the box.


2. Painless RSA: Key Generation, Signatures & Auto-Chunking

One notorious limitation of raw RSA is that it cannot encrypt data larger than its key size minus padding overhead. Usually, developers must write chunking and buffer reassembly logic. go-think/openssl handles this automatically:

package main

import (
    "bytes"
    "crypto"
    "fmt"
    "github.com/go-think/openssl"
)

func main() {
    // 1. Generate a 2048-bit RSA Key Pair in PEM format
    var privBuf, pubBuf bytes.Buffer
    _ = openssl.RSAGenerateKey(2048, &privBuf)
    _ = openssl.RSAGeneratePublicKey(privBuf.Bytes(), &pubBuf)

    privKey := privBuf.Bytes()
    pubKey  := pubBuf.Bytes()

    // 2. Encrypt arbitrary-length data (Automatic chunking under the hood)
    longData := []byte("A very long message that exceeds standard RSA block size limit...")
    encrypted, _ := openssl.RSAEncrypt(longData, pubKey)

    // Decrypt
    decrypted, _ := openssl.RSADecrypt(encrypted, privKey)
    fmt.Println("Decrypted RSA:", string(decrypted))

    // 3. Digital Signatures (RSASign & RSAVerify)
    signature, _ := openssl.RSASign(longData, privKey, crypto.SHA256)

    err := openssl.RSAVerify(longData, signature, pubKey, crypto.SHA256)
    if err == nil {
        fmt.Println("Signature verified successfully!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: RSAEncrypt also seamlessly accepts X.509 CERTIFICATE PEM blocks!


3. One-Liner Hashes & HMACs

Stop creating hash.Hash interfaces just to get a hex digest:

// MD5
hexMD5 := openssl.Md5ToString("hello world")

// SHA-256
hexSHA := openssl.Sha256ToString("hello world")

// HMAC-SHA256
hmacStr := openssl.HmacSha256ToString("secret-key", "hello world")
Enter fullscreen mode Exit fullscreen mode

🌐 100% Interoperability: Go vs. PHP vs. Java

Let's test if the ciphertext generated by Go matches other platforms bit-for-bit.

In Go (go-think/openssl):

src := []byte("123456")
key := []byte("1234567890123456")
iv  := []byte("1234567890123456")

dst, _ := openssl.AesCBCEncrypt(src, key, iv, openssl.PKCS7_PADDING)
fmt.Println(base64.StdEncoding.EncodeToString(dst))
// Output: 1jdzWuniG6UMtoa3T6uNLA==
Enter fullscreen mode Exit fullscreen mode

In PHP (openssl_encrypt):

<?php
$data = "123456";
$key  = "1234567890123456";
$iv   = "1234567890123456";

$encrypted = openssl_encrypt($data, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $iv);
echo base64_encode($encrypted);
// Output: 1jdzWuniG6UMtoa3T6uNLA== (Identical!)
Enter fullscreen mode Exit fullscreen mode

In Java (javax.crypto):

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(), "AES"), new IvParameterSpec(iv.getBytes()));
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
System.out.println(Base64.getEncoder().encodeToString(encrypted));
// Output: 1jdzWuniG6UMtoa3T6uNLA== (Identical!)
Enter fullscreen mode Exit fullscreen mode

📦 Summary

If you are building Go microservices that need to interact with external APIs, legacy systems, mobile apps, or other backends without wrestling with cryptographic discrepancies, go-think/openssl will save you hours of debugging.

Give it a ⭐ on GitHub if you find it useful, and feel free to submit issues or pull requests!

Top comments (0)