mirror of
https://github.com/go-i2p/go-i2cp.git
synced 2025-12-20 15:15:53 -05:00
224 lines
7.1 KiB
Go
224 lines
7.1 KiB
Go
// Migration Notes (November 24, 2025):
|
|
// Migrated to use github.com/go-i2p/crypto/chacha20poly1305 package which provides
|
|
// standardized ChaCha20-Poly1305 AEAD (Authenticated Encryption with Associated Data).
|
|
//
|
|
// Key Changes:
|
|
// - ChaCha20Poly1305Cipher now wraps crypto/chacha20poly1305.AEAD
|
|
// - NewChaCha20Poly1305Cipher() delegates to chacha20poly1305.GenerateKey() and NewAEAD()
|
|
// - Encrypt/Decrypt maintain [nonce][ciphertext+tag] format for backward compatibility
|
|
// - Crypto package separates tag from ciphertext; wrapper combines them for I2CP compatibility
|
|
//
|
|
// The crypto package uses explicit tag separation (AEAD standard), while I2CP traditionally
|
|
// combines nonce+ciphertext+tag. The wrapper maintains I2CP format compatibility.
|
|
|
|
package go_i2cp
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
cryptoaead "github.com/go-i2p/crypto/chacha20poly1305"
|
|
)
|
|
|
|
// ChaCha20Poly1305Cipher provides authenticated encryption using ChaCha20-Poly1305
|
|
// Wraps github.com/go-i2p/crypto/chacha20poly1305.AEAD
|
|
type ChaCha20Poly1305Cipher struct {
|
|
algorithmType uint32
|
|
aead *cryptoaead.AEAD
|
|
key [32]byte
|
|
}
|
|
|
|
// NewChaCha20Poly1305Cipher creates a new ChaCha20-Poly1305 cipher with a random key
|
|
// Delegates to github.com/go-i2p/crypto/chacha20poly1305.GenerateKey() and NewAEAD()
|
|
func NewChaCha20Poly1305Cipher() (*ChaCha20Poly1305Cipher, error) {
|
|
// Generate random 256-bit key using crypto package
|
|
key, err := cryptoaead.GenerateKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate ChaCha20-Poly1305 key: %w", err)
|
|
}
|
|
|
|
return NewChaCha20Poly1305CipherWithKey(key)
|
|
}
|
|
|
|
// NewChaCha20Poly1305CipherWithKey creates a new ChaCha20-Poly1305 cipher with the provided key
|
|
// Delegates to github.com/go-i2p/crypto/chacha20poly1305.NewAEAD()
|
|
func NewChaCha20Poly1305CipherWithKey(key [32]byte) (*ChaCha20Poly1305Cipher, error) {
|
|
aead, err := cryptoaead.NewAEAD(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create ChaCha20-Poly1305 AEAD: %w", err)
|
|
}
|
|
|
|
return &ChaCha20Poly1305Cipher{
|
|
algorithmType: CHACHA20_POLY1305,
|
|
aead: aead,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
|
|
// ChaCha20Poly1305CipherFromStream reads a ChaCha20-Poly1305 cipher from a stream
|
|
func ChaCha20Poly1305CipherFromStream(stream *Stream) (*ChaCha20Poly1305Cipher, error) {
|
|
var algorithmType uint32
|
|
var err error
|
|
|
|
algorithmType, err = stream.ReadUint32()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read algorithm type: %w", err)
|
|
}
|
|
|
|
if algorithmType != CHACHA20_POLY1305 {
|
|
return nil, fmt.Errorf("unsupported algorithm type: %d", algorithmType)
|
|
}
|
|
|
|
var key [32]byte
|
|
_, err = stream.Read(key[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read ChaCha20-Poly1305 key: %w", err)
|
|
}
|
|
|
|
return NewChaCha20Poly1305CipherWithKey(key)
|
|
}
|
|
|
|
// Encrypt encrypts plaintext with optional associated data using ChaCha20-Poly1305
|
|
// Returns [nonce][ciphertext+tag] format for I2CP compatibility
|
|
// Uses github.com/go-i2p/crypto/chacha20poly1305.AEAD.Encrypt()
|
|
func (c *ChaCha20Poly1305Cipher) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
|
|
if c.aead == nil {
|
|
return nil, fmt.Errorf("cipher not initialized")
|
|
}
|
|
|
|
// Generate random nonce using crypto package
|
|
nonce, err := cryptoaead.GenerateNonce()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate nonce: %w", err)
|
|
}
|
|
|
|
// Encrypt plaintext - crypto package returns separate ciphertext and tag
|
|
ciphertext, tag, err := c.aead.Encrypt(plaintext, additionalData, nonce[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encrypt: %w", err)
|
|
}
|
|
|
|
// Combine nonce, ciphertext, and tag for I2CP compatibility
|
|
// Format: [nonce][ciphertext][tag]
|
|
result := make([]byte, len(nonce)+len(ciphertext)+len(tag))
|
|
copy(result, nonce[:])
|
|
copy(result[len(nonce):], ciphertext)
|
|
copy(result[len(nonce)+len(ciphertext):], tag[:])
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// EncryptStream encrypts data from source stream and writes to destination stream
|
|
func (c *ChaCha20Poly1305Cipher) EncryptStream(src, dst *Stream, additionalData []byte) error {
|
|
if src == nil || dst == nil {
|
|
return fmt.Errorf("source and destination streams cannot be nil")
|
|
}
|
|
|
|
plaintext := src.Bytes()
|
|
ciphertext, err := c.Encrypt(plaintext, additionalData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt stream: %w", err)
|
|
}
|
|
|
|
_, err = dst.Write(ciphertext)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write encrypted data to stream: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Decrypt decrypts ciphertext with optional associated data using ChaCha20-Poly1305
|
|
// Expects [nonce][ciphertext+tag] format for I2CP compatibility
|
|
// Uses github.com/go-i2p/crypto/chacha20poly1305.AEAD.Decrypt()
|
|
func (c *ChaCha20Poly1305Cipher) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
|
|
if c.aead == nil {
|
|
return nil, fmt.Errorf("cipher not initialized")
|
|
}
|
|
|
|
nonceSize := cryptoaead.NonceSize
|
|
tagSize := cryptoaead.TagSize
|
|
minSize := nonceSize + tagSize
|
|
|
|
if len(ciphertext) < minSize {
|
|
return nil, fmt.Errorf("ciphertext too short: need at least %d bytes (nonce + tag)", minSize)
|
|
}
|
|
|
|
// Extract nonce, ciphertext, and tag from combined format
|
|
nonce := ciphertext[:nonceSize]
|
|
actualCiphertextWithTag := ciphertext[nonceSize:]
|
|
|
|
// Split ciphertext and tag
|
|
actualCiphertext := actualCiphertextWithTag[:len(actualCiphertextWithTag)-tagSize]
|
|
var tag [cryptoaead.TagSize]byte
|
|
copy(tag[:], actualCiphertextWithTag[len(actualCiphertext):])
|
|
|
|
// Decrypt using crypto package - pass tag separately
|
|
plaintext, err := c.aead.Decrypt(actualCiphertext, tag[:], additionalData, nonce)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decrypt: %w", err)
|
|
}
|
|
|
|
return plaintext, nil
|
|
}
|
|
|
|
// DecryptStream decrypts data from source stream and writes to destination stream
|
|
func (c *ChaCha20Poly1305Cipher) DecryptStream(src, dst *Stream, additionalData []byte) error {
|
|
if src == nil || dst == nil {
|
|
return fmt.Errorf("source and destination streams cannot be nil")
|
|
}
|
|
|
|
ciphertext := src.Bytes()
|
|
plaintext, err := c.Decrypt(ciphertext, additionalData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decrypt stream: %w", err)
|
|
}
|
|
|
|
_, err = dst.Write(plaintext)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write decrypted data to stream: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteToStream writes the cipher key to a stream
|
|
func (c *ChaCha20Poly1305Cipher) WriteToStream(stream *Stream) error {
|
|
if stream == nil {
|
|
return fmt.Errorf("stream cannot be nil")
|
|
}
|
|
|
|
// Write algorithm type
|
|
err := stream.WriteUint32(c.algorithmType)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write algorithm type: %w", err)
|
|
}
|
|
|
|
// Write key
|
|
_, err = stream.Write(c.key[:])
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write ChaCha20-Poly1305 key: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Key returns a copy of the encryption key
|
|
func (c *ChaCha20Poly1305Cipher) Key() [32]byte {
|
|
return c.key
|
|
}
|
|
|
|
// AlgorithmType returns the algorithm type constant
|
|
func (c *ChaCha20Poly1305Cipher) AlgorithmType() uint32 {
|
|
return c.algorithmType
|
|
}
|
|
|
|
// NonceSize returns the nonce size used by the cipher
|
|
func (c *ChaCha20Poly1305Cipher) NonceSize() int {
|
|
return cryptoaead.NonceSize
|
|
}
|
|
|
|
// Overhead returns the authentication tag overhead
|
|
func (c *ChaCha20Poly1305Cipher) Overhead() int {
|
|
return cryptoaead.TagSize
|
|
}
|