mcrypt-go

A Go library for Rijndael-256 encryption and decryption, designed to provide full compatibility with PHP's legacy mcrypt extension (MCRYPT_RIJNDAEL_256).
Why does this library exist?
Go's standard crypto/aes package implements the FIPS 197 AES standard. Although AES is based on Rijndael, AES strictly limits the block size to 128 bits (16 bytes), regardless of key length.
However, PHP's legacy mcrypt extension allowed using Rijndael with a 256-bit block size (32 bytes) via the MCRYPT_RIJNDAEL_256 constant.
Standard Go does not support 256-bit blocks. Attempting to decrypt data encrypted with MCRYPT_RIJNDAEL_256 using crypto/aes will fail or produce corrupted output.
This library implements the Rijndael-256 block cipher natively in Go, conforming to the standard cipher.Block interface, with ECB and CBC modes that exactly match PHP's behavior (including NUL-byte padding).
Features
- 100% PHP
mcrypt compatibility: Encrypts and decrypts data identically to mcrypt_decrypt / mcrypt_encrypt with MCRYPT_RIJNDAEL_256.
- All standard block cipher modes: ECB, CBC, CFB, OFB, and CTR with a 32-byte (256-bit) block size.
- NUL-byte padding: Matches PHP mcrypt's default padding behavior.
- Standard Go interface: Implements
cipher.Block for interoperability with Go's crypto tooling.
- URL-safe Base64: Handles URL-safe Base64 payloads (swapping
-/_ for +//).
- Cryptographically secure IV generation:
GenerateIV() using crypto/rand.
- Stream cipher wrapper:
NewStreamWriter / NewStreamReader for chunk-based encryption of large data.
- Sensitive memory clearing:
ClearMem for timing-safe zeroing of sensitive data.
- Specific error types:
ErrInvalidIV, ErrInvalidKey, ErrInvalidNonce, ErrInvalidCiphertext, ErrEmptyPassword.
Installation
go get codeberg.org/simplicer/mcrypt-go
Usage
PHP mcrypt interoperability
If you're migrating a PHP API that encrypts credentials like this:
function decrypt($psw, $data){
$data = str_replace(array('-','_'), array('+','/'), $data);
$mod4 = strlen($data) % 4;
if ($mod4) $data .= substr('====', $mod4);
$decoded = base64_decode($data);
return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $psw, $decoded, MCRYPT_MODE_ECB);
}
The exact equivalent in Go:
package main
import (
"fmt"
"log"
"codeberg.org/simplicer/mcrypt-go"
)
func main() {
partnerPassword := "h3SdkVj2#!dkj3-D$5P"
payload := "TQ3qLcs6FmgOBel-AAOhO97uE4SCRvj-uVbABOM_uNVF6k8JjwbLF1i7OFTfvedWGh9ZnMnW1PoKNmCKiYWT57kPmOVJC8Qi6K0wtKQxfbtenWCBv8ZricPjXXdJnvuPAvIRdDKO9wodxM_6OiSQp_8Dzku-3UMCRYo1cBCKv1Y"
decrypted, err := mcrypt.DecryptPHP(partnerPassword, payload)
if err != nil {
log.Fatalf("decrypt error: %v", err)
}
fmt.Printf("Decrypted: %s\n", decrypted)
// Output: {"password":"r3fZNZmHL2ra3h!","auth":"p79itgjfhbvou4df","admin":"ARB588SF-ITNIC","tech":"ARB5TECH-ITNIC"}
encrypted, err := mcrypt.EncryptPHP(partnerPassword, decrypted)
if err != nil {
log.Fatalf("encrypt error: %v", err)
}
fmt.Printf("Encrypted: %s\n", encrypted)
}
CBC mode with IV
key := mcrypt.PadKey([]byte("my-secret-key"))
iv := make([]byte, mcrypt.BlockSize) // 32 bytes for Rijndael-256
ciphertext, err := mcrypt.EncryptCBC(key, plaintext, iv)
decrypted, err := mcrypt.DecryptCBC(key, ciphertext, iv)
result := mcrypt.UnpadNull(decrypted)
CFB mode
key := mcrypt.PadKey([]byte("my-secret-key"))
iv := make([]byte, mcrypt.BlockSize)
// Use GenerateIV() for cryptographically random IV
ciphertext, err := mcrypt.EncryptCFB(key, plaintext, iv)
decrypted, err := mcrypt.DecryptCFB(key, ciphertext, iv)
OFB mode
ciphertext, err := mcrypt.EncryptOFB(key, plaintext, iv)
decrypted, err := mcrypt.DecryptOFB(key, ciphertext, iv) // symmetric
CTR mode
nonce := make([]byte, mcrypt.BlockSize) // or use mcrypt.GenerateIV()
ciphertext, err := mcrypt.EncryptCTR(key, plaintext, nonce)
decrypted, err := mcrypt.DecryptCTR(key, ciphertext, nonce) // symmetric
Stream cipher (large data)
w := mcrypt.NewStreamWriter(key, mcrypt.CTR, nonce, os.Stdout)
defer w.Close()
io.Copy(w, largeFile)
r := mcrypt.NewStreamReader(key, mcrypt.CTR, nonce, encryptedFile)
io.Copy(os.Stdout, r)
Secure IV generation
iv, err := mcrypt.GenerateIV() // 32 bytes from crypto/rand
if err != nil {
log.Fatal(err)
}
// Use iv with EncryptCBC/EncryptCFB/EncryptOFB — never reuse an IV
Memory clearing
mcrypt.ClearMem(sensitiveData) // zero memory timing-safely
Raw block cipher access
For advanced use cases where you need the raw Rijndael-256 block cipher:
block, err := mcrypt.NewCipher(key) // key must be 16, 24, or 32 bytes
if err != nil {
log.Fatal(err)
}
// Use with any crypto/cipher mode that accepts cipher.Block.
// Remember the block size is always 32 bytes:
// cipher.NewCFBEncrypter(block, iv32bytes)
API reference
| Function |
Description |
EncryptPHP(password, plaintext) |
Encrypt to URL-safe Base64 (PHP compatible) |
DecryptPHP(password, payload) |
Decrypt from URL-safe Base64 (PHP compatible) |
EncryptECB(key, plaintext) |
Encrypt in ECB mode |
DecryptECB(key, ciphertext) |
Decrypt in ECB mode |
EncryptCBC(key, plaintext, iv) |
Encrypt in CBC mode |
DecryptCBC(key, ciphertext, iv) |
Decrypt in CBC mode |
EncryptCFB(key, plaintext, iv) |
Encrypt in CFB mode |
DecryptCFB(key, ciphertext, iv) |
Decrypt in CFB mode |
EncryptOFB(key, plaintext, iv) |
Encrypt in OFB mode |
DecryptOFB(key, ciphertext, iv) |
Decrypt in OFB mode |
EncryptCTR(key, plaintext, nonce) |
Encrypt in CTR mode |
DecryptCTR(key, ciphertext, nonce) |
Decrypt in CTR mode |
GenerateIV() |
Generate cryptographically secure random IV |
NewStreamWriter(key, mode, iv, w) |
Stream encrypt large data to an io.Writer |
NewStreamReader(key, mode, iv, r) |
Stream decrypt large data from an io.Reader |
ClearMem(b) |
Zero memory timing-safely using crypto/subtle |
PadKey(key) |
Pad key to 16/24/32 bytes with NUL |
PadNull(data) |
Pad data to block boundary with NUL |
UnpadNull(data) |
Strip trailing NUL bytes |
NewCipher(key) |
Get raw cipher.Block for advanced use |
Error types
| Error |
When returned |
ErrInvalidIV |
IV is not 32 bytes |
ErrInvalidKey |
Key is not 16, 24, or 32 bytes |
ErrInvalidNonce |
CTR nonce is not 32 bytes |
ErrInvalidCiphertext |
Ciphertext length is not a multiple of 32 |
ErrEmptyPassword |
Partner password is empty |
Testing
go test -v ./...
Versioning
This project follows Semantic Versioning with Conventional Commits. See CHANGELOG.md for details.
License
GNU General Public License v3.0 (GPL-3.0) — a strong copyleft license. Any application or library that uses or incorporates this package must be published and distributed under the same open-source terms. See LICENSE for details.
Author
Antonio Villamarin — antonio [at] simplicer [dot] com