░█████╗░██████╗░██╗░░░██╗██████╗░████████╗░█████╗░
██╔══██╗██╔══██╗╚██╗░██╔╝██╔══██╗╚══██╔══╝██╔══██╗
██║░░╚═╝██████╔╝░╚████╔╝░██████╔╝░░░██║░░░██║░░██║
██║░░██╗██╔══██╗░░╚██╔╝░░██╔═══╝░░░░██║░░░██║░░██║
╚█████╔╝██║░░██║░░░██║░░░██║░░░░░░░░██║░░░╚█████╔╝
░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░░░░░░╚═╝░░░░╚════╝░
crypto provides key generation, encryption, digital signatures, and password hashing for Go applications.
go get ella.to/crypto@v0.0.2Generate a public/private key pair for encryption and signing. Keys are Curve25519 for key exchange and Ed25519 for signatures.
pub, priv, err := crypto.GenerateKey()Keys can be encoded to bytes, hex strings, and support JSON marshaling out of the box:
// To hex string
fmt.Println(pub.String()) // "a1b2c3..."
fmt.Println(priv.String()) // "d4e5f6..."
// From hex string
pub, err := crypto.ParsePublicKey("a1b2c3...")
priv, err := crypto.ParsePrivateKey("d4e5f6...")
// Binary encode/decode
data, _ := pub.Encode() // 64 bytes
err = pub.Decode(data)
data, _ = priv.Encode() // 96 bytes
err = priv.Decode(data)Both key types implement encoding.TextMarshaler and encoding.TextUnmarshaler, so they work directly with json.Marshal/json.Unmarshal.
h, err := pub.Hash() // returns a hash.Hash (SHA-256 of the public key bytes)Encrypt and decrypt data using a shared 32-byte key. Uses NaCl secretbox under the hood — each message gets a random 24-byte nonce prepended to the ciphertext.
var key [32]byte
// ... fill key ...
ciphertext, err := crypto.Encrypt(key, plaintext)
plaintext, err := crypto.Decrypt(key, ciphertext)Two parties can derive the same shared key from their key pairs. This is the typical pattern for end-to-end encryption:
pubA, privA, _ := crypto.GenerateKey()
pubB, privB, _ := crypto.GenerateKey()
// Both sides derive the same key
sharedA := privA.SharedKey(pubB)
sharedB := privB.SharedKey(pubA)
// sharedA == sharedB
ciphertext, _ := crypto.Encrypt(sharedA, []byte("secret message"))
plaintext, _ := crypto.Decrypt(sharedB, ciphertext)For large data, use streaming encryption to avoid loading everything into memory. Data is processed in blocks of a configurable size:
// Encrypt from reader to writer
n, err := crypto.EncryptStream(key, 1024, writer, reader)
// Decrypt from reader to writer
n, err := crypto.DecryptStream(key, 1024, writer, reader)The block size must match between encryption and decryption. Each block is independently encrypted, so you get the overhead of a nonce + authentication tag per block.
The overhead per block is defined by crypto.EncryptionOverhead (24 bytes nonce + NaCl box overhead).
Sign data with a private key and verify with the corresponding public key:
pub, priv, _ := crypto.GenerateKey()
signed := priv.Sign([]byte("important data"))
fmt.Println(signed.String()) // hex-encoded signature
valid := pub.Verify(signed) // trueThe Signed type also supports text marshaling for JSON serialization.
Hash and verify passwords using bcrypt. Supports optional salt and pepper for additional security:
// Basic usage
hashed, err := crypto.HashPassword("my-password")
ok := crypto.CheckPasswordHash(hashed, "my-password") // true
// With salt and pepper
hashed, err := crypto.HashPassword("my-password",
crypto.WithSalt("user-specific-salt"),
crypto.WithPepper("app-secret-pepper"),
)
ok := crypto.CheckPasswordHash(hashed, "my-password",
crypto.WithSalt("user-specific-salt"),
crypto.WithPepper("app-secret-pepper"),
)
// Custom bcrypt cost
hashed, err := crypto.HashPassword("my-password",
crypto.WithCost(12),
)The salt and pepper values are concatenated with the password before hashing (salt + password + pepper), so the same options must be provided for both hashing and verification.
MIT — see LICENSE for details.