mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-07-13 11:54:46 -04:00
6
Makefile
6
Makefile
@ -16,12 +16,16 @@ else
|
||||
EXE := $(REPO)/go-i2p
|
||||
endif
|
||||
|
||||
#check for gofumpt
|
||||
check_gofumpt:
|
||||
@which gofumpt > /dev/null 2>&1 || (echo "gofumpt is required but not installed. Please install it from https://github.com/mvdan/gofumpt."; exit 1)
|
||||
|
||||
build: clean $(EXE)
|
||||
|
||||
$(EXE):
|
||||
$(GO) build --tags netgo,osusergo -v -o $(EXE)
|
||||
|
||||
test: fmt
|
||||
test: check_gofumpt fmt
|
||||
$(GO) test -v -failfast ./lib/common/...
|
||||
|
||||
clean:
|
||||
|
@ -1 +1,107 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AESSymmetricKey represents a symmetric key for AES encryption/decryption
|
||||
type AESSymmetricKey struct {
|
||||
Key []byte // AES key (must be 16, 24, or 32 bytes for AES-128, AES-192, AES-256)
|
||||
IV []byte // Initialization Vector (must be 16 bytes for AES)
|
||||
}
|
||||
|
||||
// AESSymmetricEncrypter implements the Encrypter interface using AES
|
||||
type AESSymmetricEncrypter struct {
|
||||
Key []byte
|
||||
IV []byte
|
||||
}
|
||||
|
||||
// Encrypt encrypts data using AES-CBC with PKCS#7 padding
|
||||
func (e *AESSymmetricEncrypter) Encrypt(data []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(e.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plaintext := pkcs7Pad(data, aes.BlockSize)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
mode := cipher.NewCBCEncrypter(block, e.IV)
|
||||
mode.CryptBlocks(ciphertext, plaintext)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// AESSymmetricDecrypter implements the Decrypter interface using AES
|
||||
type AESSymmetricDecrypter struct {
|
||||
Key []byte
|
||||
IV []byte
|
||||
}
|
||||
|
||||
// Decrypt decrypts data using AES-CBC with PKCS#7 padding
|
||||
func (d *AESSymmetricDecrypter) Decrypt(data []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(d.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data)%aes.BlockSize != 0 {
|
||||
return nil, fmt.Errorf("ciphertext is not a multiple of the block size")
|
||||
}
|
||||
|
||||
plaintext := make([]byte, len(data))
|
||||
mode := cipher.NewCBCDecrypter(block, d.IV)
|
||||
mode.CryptBlocks(plaintext, data)
|
||||
|
||||
plaintext, err = pkcs7Unpad(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// NewEncrypter creates a new AESSymmetricEncrypter
|
||||
func (k *AESSymmetricKey) NewEncrypter() (Encrypter, error) {
|
||||
return &AESSymmetricEncrypter{
|
||||
Key: k.Key,
|
||||
IV: k.IV,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Len returns the length of the key
|
||||
func (k *AESSymmetricKey) Len() int {
|
||||
return len(k.Key)
|
||||
}
|
||||
|
||||
// NewDecrypter creates a new AESSymmetricDecrypter
|
||||
func (k *AESSymmetricKey) NewDecrypter() (Decrypter, error) {
|
||||
return &AESSymmetricDecrypter{
|
||||
Key: k.Key,
|
||||
IV: k.IV,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pkcs7Pad(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - (len(data) % blockSize)
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs7Unpad(data []byte) ([]byte, error) {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil, fmt.Errorf("data is empty")
|
||||
}
|
||||
padding := int(data[length-1])
|
||||
if padding == 0 || padding > aes.BlockSize {
|
||||
return nil, fmt.Errorf("invalid padding")
|
||||
}
|
||||
paddingStart := length - padding
|
||||
for i := paddingStart; i < length; i++ {
|
||||
if data[i] != byte(padding) {
|
||||
return nil, fmt.Errorf("invalid padding")
|
||||
}
|
||||
}
|
||||
return data[:paddingStart], nil
|
||||
}
|
||||
|
184
lib/crypto/aes_test.go
Normal file
184
lib/crypto/aes_test.go
Normal file
@ -0,0 +1,184 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestAESEncryptDecrypt(t *testing.T) {
|
||||
key := make([]byte, 32) // 256-bit key
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate random key: %v", err)
|
||||
}
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate random IV: %v", err)
|
||||
}
|
||||
|
||||
symmetricKey := AESSymmetricKey{
|
||||
Key: key,
|
||||
IV: iv,
|
||||
}
|
||||
|
||||
encrypter, err := symmetricKey.NewEncrypter()
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating encrypter: %v", err)
|
||||
}
|
||||
|
||||
decrypter, err := symmetricKey.NewDecrypter()
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating decrypter: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
plaintext []byte
|
||||
}{
|
||||
{"Empty string", []byte("")},
|
||||
{"Short string", []byte("Hello, World!")},
|
||||
{"Long string", bytes.Repeat([]byte("A"), 1000)},
|
||||
{"Exact block size", bytes.Repeat([]byte("A"), aes.BlockSize)},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ciphertext, err := encrypter.Encrypt(tc.plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encryption failed: %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := decrypter.Decrypt(ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decryption failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(tc.plaintext, decrypted) {
|
||||
t.Errorf("Decrypted text doesn't match original plaintext.\nOriginal: %s\nDecrypted: %s",
|
||||
hex.EncodeToString(tc.plaintext), hex.EncodeToString(decrypted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESEncryptInvalidKey(t *testing.T) {
|
||||
invalidKeys := [][]byte{
|
||||
make([]byte, 15), // Too short
|
||||
make([]byte, 17), // Invalid length
|
||||
make([]byte, 31), // Too short for AES-256
|
||||
make([]byte, 33), // Too long
|
||||
make([]byte, 0), // Empty
|
||||
nil, // Nil
|
||||
}
|
||||
|
||||
plaintext := []byte("Test plaintext")
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
_, _ = rand.Read(iv)
|
||||
|
||||
for _, key := range invalidKeys {
|
||||
symmetricKey := &AESSymmetricKey{
|
||||
Key: key,
|
||||
IV: iv,
|
||||
}
|
||||
encrypter, err := symmetricKey.NewEncrypter()
|
||||
if err == nil {
|
||||
_, err = encrypter.Encrypt(plaintext)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for invalid key length %d, but got none", len(key))
|
||||
} else {
|
||||
t.Logf("Correctly got error for key length %d: %v", len(key), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESDecryptInvalidInput(t *testing.T) {
|
||||
key := make([]byte, 32) // Valid key length for AES-256
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
_, _ = rand.Read(key)
|
||||
_, _ = rand.Read(iv)
|
||||
|
||||
symmetricKey := &AESSymmetricKey{
|
||||
Key: key,
|
||||
IV: iv,
|
||||
}
|
||||
decrypter, err := symmetricKey.NewDecrypter()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create decrypter: %v", err)
|
||||
}
|
||||
|
||||
invalidCiphertexts := [][]byte{
|
||||
make([]byte, 15), // Not a multiple of block size
|
||||
make([]byte, 0), // Empty
|
||||
nil, // Nil
|
||||
}
|
||||
|
||||
for _, ciphertext := range invalidCiphertexts {
|
||||
_, err := decrypter.Decrypt(ciphertext)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for invalid ciphertext length %d, but got none", len(ciphertext))
|
||||
} else {
|
||||
t.Logf("Correctly got error for ciphertext length %d: %v", len(ciphertext), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS7PadUnpad(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
blockSize int
|
||||
}{
|
||||
{"Empty input", []byte{}, 16},
|
||||
{"Exact block size", bytes.Repeat([]byte("A"), 16), 16},
|
||||
{"One byte short", bytes.Repeat([]byte("A"), 15), 16},
|
||||
{"Multiple blocks", bytes.Repeat([]byte("A"), 32), 16},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
padded := pkcs7Pad(tc.input, tc.blockSize)
|
||||
if len(padded)%tc.blockSize != 0 {
|
||||
t.Errorf("Padded data length (%d) is not a multiple of block size (%d)", len(padded), tc.blockSize)
|
||||
}
|
||||
|
||||
unpadded, err := pkcs7Unpad(padded)
|
||||
if err != nil {
|
||||
t.Fatalf("Unpadding failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(tc.input, unpadded) {
|
||||
t.Errorf("Unpadded data doesn't match original input.\nOriginal: %s\nUnpadded: %s",
|
||||
hex.EncodeToString(tc.input), hex.EncodeToString(unpadded))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS7UnpadInvalidInput(t *testing.T) {
|
||||
invalidInputs := []struct {
|
||||
name string
|
||||
input []byte
|
||||
}{
|
||||
{"Empty slice", []byte{}},
|
||||
{"Invalid padding value", []byte{1, 2, 3, 4, 0}}, // Padding value 0 is invalid
|
||||
{"Padding larger than block size", append(bytes.Repeat([]byte{17}, 17))}, // Padding value 17 (>16) is invalid
|
||||
{"Incorrect padding bytes", []byte{1, 2, 3, 4, 5, 6, 2, 3, 3}}, // Last padding bytes do not match padding value
|
||||
{"Valid block size but invalid padding", append(bytes.Repeat([]byte{1}, 15), 3)}, // Padding value 3, but bytes are 1
|
||||
}
|
||||
|
||||
for _, tc := range invalidInputs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := pkcs7Unpad(tc.input)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for invalid input %v, but got none", tc.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
--
|
||||
import "github.com/go-i2p/go-i2p/lib/crypto"
|
||||
|
||||
package for i2p specific crpytography
|
||||
package for i2p specific cryptography
|
||||
|
||||
## Usage
|
||||
|
||||
@ -12,7 +12,72 @@ const (
|
||||
OPAD = byte(0x5C)
|
||||
)
|
||||
```
|
||||
#### type AESSymmetricKey
|
||||
```go
|
||||
type AESSymmetricKey struct {
|
||||
Key []byte // AES key (must be 16, 24, or 32 bytes for AES-128, AES-192, AES-256)
|
||||
IV []byte // Initialization Vector (must be 16 bytes for AES)
|
||||
}
|
||||
```
|
||||
|
||||
AESSymmetricKey represents a symmetric key for AES encryption/decryption
|
||||
|
||||
#### func (AESSymmetricKey) NewEncrypter
|
||||
|
||||
```go
|
||||
func (k *AESSymmetricKey) NewEncrypter() (Encrypter, error)
|
||||
```
|
||||
NewEncrypter creates a new AESSymmetricEncrypter
|
||||
|
||||
#### func (AESSymmetricKey) NewDecrypter
|
||||
|
||||
```go
|
||||
func (k *AESSymmetricKey) NewDecrypter() (Decrypter, error)
|
||||
```
|
||||
NewDecrypter creates a new AESSymmetricDecrypter
|
||||
|
||||
#### func (AESSymmetricKey) Len
|
||||
|
||||
```go
|
||||
func (k *AESSymmetricKey) Len() int
|
||||
```
|
||||
Len returns the length of the key
|
||||
|
||||
#### type AESSymmetricEncrypter
|
||||
|
||||
```go
|
||||
type AESSymmetricEncrypter struct {
|
||||
Key []byte
|
||||
IV []byte
|
||||
}
|
||||
```
|
||||
|
||||
AESSymmetricEncrypter implements the Encrypter interface using AES
|
||||
|
||||
#### func (*AESSymmetricEncrypter) Encrypt
|
||||
|
||||
```go
|
||||
func (e *AESSymmetricEncrypter) Encrypt(data []byte) ([]byte, error)
|
||||
```
|
||||
Encrypt encrypts data using AES-CBC with PKCS#7 padding
|
||||
|
||||
#### type AESSymmetricDecrypter
|
||||
|
||||
```go
|
||||
type AESSymmetricDecrypter struct {
|
||||
Key []byte
|
||||
IV []byte
|
||||
}
|
||||
```
|
||||
|
||||
AESSymmetricDecrypter implements the Decrypter interface using AES
|
||||
|
||||
#### func (*AESSymmetricDecrypter) Decrypt
|
||||
|
||||
```go
|
||||
func (d *AESSymmetricDecrypter) Decrypt(data []byte) ([]byte, error)
|
||||
```
|
||||
Decrypt decrypts data using AES-CBC with PKCS#7 padding
|
||||
```go
|
||||
var (
|
||||
ElgDecryptFail = errors.New("failed to decrypt elgamal encrypted data")
|
||||
|
@ -35,8 +35,8 @@ type Transport interface {
|
||||
// returns nil and an error on error
|
||||
GetSession(routerInfo router_info.RouterInfo) (TransportSession, error)
|
||||
|
||||
// return true if a routerInfo is compatable with this transport
|
||||
Compatable(routerInfo router_info.RouterInfo) bool
|
||||
// return true if a routerInfo is compatible with this transport
|
||||
Compatible(routerInfo router_info.RouterInfo) bool
|
||||
|
||||
// close the transport cleanly
|
||||
// blocks until done
|
||||
@ -72,12 +72,12 @@ func (tmux *TransportMuxer) Close() (err error)
|
||||
```
|
||||
close every transport that this transport muxer has
|
||||
|
||||
#### func (*TransportMuxer) Compatable
|
||||
#### func (*TransportMuxer) Compatible
|
||||
|
||||
```go
|
||||
func (tmux *TransportMuxer) Compatable(routerInfo router_info.RouterInfo) (compat bool)
|
||||
func (tmux *TransportMuxer) Compatible(routerInfo router_info.RouterInfo) (compat bool)
|
||||
```
|
||||
is there a transport that we mux that is compatable with this router info?
|
||||
is there a transport that we mux that is compatible with this router info?
|
||||
|
||||
#### func (*TransportMuxer) GetSession
|
||||
|
||||
|
@ -57,7 +57,7 @@ func (tmux *TransportMuxer) Name() string {
|
||||
func (tmux *TransportMuxer) GetSession(routerInfo router_info.RouterInfo) (s TransportSession, err error) {
|
||||
for _, t := range tmux.trans {
|
||||
// pick the first one that is compatable
|
||||
if t.Compatable(routerInfo) {
|
||||
if t.Compatible(routerInfo) {
|
||||
// try to get a session
|
||||
s, err = t.GetSession(routerInfo)
|
||||
if err != nil {
|
||||
@ -75,9 +75,9 @@ func (tmux *TransportMuxer) GetSession(routerInfo router_info.RouterInfo) (s Tra
|
||||
}
|
||||
|
||||
// is there a transport that we mux that is compatable with this router info?
|
||||
func (tmux *TransportMuxer) Compatable(routerInfo router_info.RouterInfo) (compat bool) {
|
||||
func (tmux *TransportMuxer) Compatible(routerInfo router_info.RouterInfo) (compat bool) {
|
||||
for _, t := range tmux.trans {
|
||||
if t.Compatable(routerInfo) {
|
||||
if t.Compatible(routerInfo) {
|
||||
compat = true
|
||||
return
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ type Transport interface {
|
||||
// returns nil and an error on error
|
||||
GetSession(routerInfo router_info.RouterInfo) (TransportSession, error)
|
||||
|
||||
// return true if a routerInfo is compatable with this transport
|
||||
Compatable(routerInfo router_info.RouterInfo) bool
|
||||
// return true if a routerInfo is compatible with this transport
|
||||
Compatible(routerInfo router_info.RouterInfo) bool
|
||||
|
||||
// close the transport cleanly
|
||||
// blocks until done
|
||||
|
Reference in New Issue
Block a user