pax_global_header00006660000000000000000000000064150661205770014523gustar00rootroot0000000000000052 comment=a582407059febb953c1d3132209c5fa19090d826 golang-github-jedisct1-go-ipcrypt-0.1.1/000077500000000000000000000000001506612057700200505ustar00rootroot00000000000000golang-github-jedisct1-go-ipcrypt-0.1.1/.github/000077500000000000000000000000001506612057700214105ustar00rootroot00000000000000golang-github-jedisct1-go-ipcrypt-0.1.1/.github/workflows/000077500000000000000000000000001506612057700234455ustar00rootroot00000000000000golang-github-jedisct1-go-ipcrypt-0.1.1/.github/workflows/go.yml000066400000000000000000000012071506612057700245750ustar00rootroot00000000000000# This workflow will build a golang project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go name: Go on: push: branches: ["main"] pull_request: jobs: build: strategy: matrix: go-version: ["1.20", "stable"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - run: go version - name: Build run: go build -v ./... - name: Test run: go test -v ./... golang-github-jedisct1-go-ipcrypt-0.1.1/.gitignore000066400000000000000000000005351506612057700220430ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) vendor/ # Go workspace file go.work # IDE specific files .idea/ .vscode/ *.swp *.swo *~ golang-github-jedisct1-go-ipcrypt-0.1.1/LICENSE000066400000000000000000000014621506612057700210600ustar00rootroot00000000000000/* * ISC License * * Copyright (c) 2025 * Frank Denis * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ golang-github-jedisct1-go-ipcrypt-0.1.1/README.md000066400000000000000000000067341506612057700213410ustar00rootroot00000000000000# Go Implementation of IPCrypt This is a Go implementation of the IP address encryption and obfuscation methods specified in the [ipcrypt document](https://datatracker.ietf.org/doc/draft-denis-ipcrypt/) ("Methods for IP Address Encryption and Obfuscation"). ## Overview The implementation provides three methods for IP address encryption: 1. **ipcrypt-deterministic**: A deterministic mode where the same input always produces the same output for a given key. 2. **ipcrypt-nd**: A non-deterministic mode that uses an 8-byte tweak for enhanced privacy. 3. **ipcrypt-ndx**: An extended non-deterministic mode that uses a 32-byte key and 16-byte tweak for increased security. ## Installation ```sh go get github.com/jedisct1/go-ipcrypt ``` ## Usage ```go package main import ( "crypto/rand" "fmt" "net" "github.com/jedisct1/go-ipcrypt" ) func main() { // Create a 16-byte key for ipcrypt-deterministic mode key := make([]byte, ipcrypt.KeySizeDeterministic) rand.Read(key) // Encrypt an IP address (ipcrypt-deterministic mode) ip := net.ParseIP("192.168.1.1") encrypted, err := ipcrypt.EncryptIP(key, ip) if err != nil { panic(err) } fmt.Printf("Encrypted: %s\n", encrypted) // Decrypt the IP address decrypted, err := ipcrypt.DecryptIP(key, encrypted) if err != nil { panic(err) } fmt.Printf("Decrypted: %s\n", decrypted) // ipcrypt-nd mode with random tweak ndKey := make([]byte, ipcrypt.KeySizeND) rand.Read(ndKey) encryptedND, err := ipcrypt.EncryptIPNonDeterministic(ip.String(), ndKey, nil) if err != nil { panic(err) } decryptedND, err := ipcrypt.DecryptIPNonDeterministic(encryptedND, ndKey) if err != nil { panic(err) } fmt.Printf("Non-deterministic decrypted: %s\n", decryptedND) // ipcrypt-ndx mode with random tweak xtsKey := make([]byte, ipcrypt.KeySizeNDX) rand.Read(xtsKey) encryptedX, err := ipcrypt.EncryptIPNonDeterministicX(ip.String(), xtsKey, nil) if err != nil { panic(err) } decryptedX, err := ipcrypt.DecryptIPNonDeterministicX(encryptedX, xtsKey) if err != nil { panic(err) } fmt.Printf("Extended non-deterministic decrypted: %s\n", decryptedX) } ``` ## API Reference ### Constants - `KeySizeDeterministic`: 16 bytes (ipcrypt-deterministic) - `KeySizeND`: 16 bytes (ipcrypt-nd) - `KeySizeNDX`: 32 bytes (ipcrypt-ndx) - `TweakSize`: 8 bytes (ipcrypt-nd tweak) - `TweakSizeX`: 16 bytes (ipcrypt-ndx tweak) ### Functions #### Deterministic Mode - `EncryptIP(key []byte, ip net.IP) (net.IP, error)` - Encrypts an IP address deterministically - `DecryptIP(key []byte, encrypted net.IP) (net.IP, error)` - Decrypts an IP address deterministically - `EncryptIPPfx(ip net.IP, key []byte) (net.IP, error)` - Encrypts an IP address with prefix preservation - `DecryptIPPfx(encryptedIP net.IP, key []byte) (net.IP, error)` - Decrypts an IP address with prefix preservation #### Non-Deterministic Mode (ipcrypt-nd) - `EncryptIPNonDeterministic(ip string, key []byte, tweak []byte) ([]byte, error)` - Encrypts with 8-byte tweak - `DecryptIPNonDeterministic(ciphertext []byte, key []byte) (string, error)` - Decrypts ipcrypt-nd ciphertext #### Extended Non-Deterministic Mode (ipcrypt-ndx) - `EncryptIPNonDeterministicX(ip string, key []byte, tweak []byte) ([]byte, error)` - Encrypts with 16-byte tweak - `DecryptIPNonDeterministicX(ciphertext []byte, key []byte) (string, error)` - Decrypts ipcrypt-ndx ciphertext golang-github-jedisct1-go-ipcrypt-0.1.1/go.mod000066400000000000000000000000571506612057700211600ustar00rootroot00000000000000module github.com/jedisct1/go-ipcrypt go 1.20 golang-github-jedisct1-go-ipcrypt-0.1.1/go.sum000066400000000000000000000000001506612057700211710ustar00rootroot00000000000000golang-github-jedisct1-go-ipcrypt-0.1.1/ipcrypt.go000066400000000000000000000356721506612057700221060ustar00rootroot00000000000000// Package ipcrypt implements IP address encryption and obfuscation methods. // It provides three encryption modes: // - ipcrypt-deterministic: A deterministic mode where the same input always produces the same output // - ipcrypt-nd: A non-deterministic mode that uses an 8-byte tweak // - ipcrypt-ndx: An extended non-deterministic mode that uses a 32-byte key and 16-byte tweak // // For non-deterministic modes, passing nil as the tweak parameter will automatically generate a random tweak. package ipcrypt import ( "crypto/aes" "crypto/rand" "crypto/subtle" "errors" "fmt" "net" ) // Key sizes for different encryption modes const ( KeySizeDeterministic = 16 // Size in bytes of the key for ipcrypt-deterministic mode KeySizeND = 16 // Size in bytes of the key for ipcrypt-nd mode KeySizeNDX = 32 // Size in bytes of the key for ipcrypt-ndx mode ) // Tweak sizes for different encryption modes const ( TweakSize = 8 // Size in bytes of the tweak for ipcrypt-nd mode TweakSizeX = 16 // Size in bytes of the tweak for ipcrypt-ndx mode ) // Error definitions for the package var ( ErrInvalidKeySize = errors.New("invalid key size") ErrInvalidIP = errors.New("invalid IP address") ErrInvalidTweak = errors.New("invalid tweak size") ) // Utility functions // validateKey checks if the key length matches the expected size func validateKey(key []byte, expectedSize int) error { if len(key) != expectedSize { return fmt.Errorf("%w: got %d bytes, want %d bytes", ErrInvalidKeySize, len(key), expectedSize) } return nil } // validateIP ensures the IP address is valid and can be converted to 16-byte form func validateIP(ip net.IP) ([]byte, error) { if ip == nil { return nil, ErrInvalidIP } ip16 := ip.To16() if ip16 == nil { return nil, ErrInvalidIP } return ip16, nil } // validateTweak checks if the tweak length matches the expected size func validateTweak(tweak []byte, expectedSize int) error { if len(tweak) != expectedSize { return fmt.Errorf("%w: got %d bytes, want %d bytes", ErrInvalidTweak, len(tweak), expectedSize) } return nil } // xorBytes performs XOR operation on two byte slices of equal length func xorBytes(a, b []byte) []byte { if len(a) != len(b) { return nil } c := make([]byte, len(a)) subtle.XORBytes(c, a, b) return c } // Deterministic mode functions // EncryptIP encrypts an IP address using ipcrypt-deterministic mode. // The key must be exactly KeySizeDeterministic bytes long. // Returns the encrypted IP address as a net.IP. func EncryptIP(key []byte, ip net.IP) (net.IP, error) { if err := validateKey(key, KeySizeDeterministic); err != nil { return nil, err } ipBytes, err := validateIP(ip) if err != nil { return nil, err } block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("failed to create cipher: %w", err) } encrypted := make([]byte, 16) block.Encrypt(encrypted, ipBytes) return net.IP(encrypted), nil } // DecryptIP decrypts an IP address that was encrypted using ipcrypt-deterministic mode. // The key must be exactly KeySizeDeterministic bytes long. // Returns the decrypted IP address as a net.IP. func DecryptIP(key []byte, encrypted net.IP) (net.IP, error) { if err := validateKey(key, KeySizeDeterministic); err != nil { return nil, err } ipBytes, err := validateIP(encrypted) if err != nil { return nil, err } block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("failed to create cipher: %w", err) } decrypted := make([]byte, 16) block.Decrypt(decrypted, ipBytes) return net.IP(decrypted), nil } // Non-deterministic mode functions // EncryptIPNonDeterministic encrypts an IP address using ipcrypt-nd mode. // The key must be exactly KeySizeND bytes long. // If tweak is nil, a random tweak will be generated. // Returns a byte slice containing the tweak concatenated with the encrypted IP. func EncryptIPNonDeterministic(ip string, key []byte, tweak []byte) ([]byte, error) { if err := validateKey(key, KeySizeND); err != nil { return nil, err } ipBytes, err := validateIP(net.ParseIP(ip)) if err != nil { return nil, err } var t []byte if tweak == nil { t = make([]byte, TweakSize) if _, err := rand.Read(t); err != nil { return nil, fmt.Errorf("failed to generate tweak: %w", err) } } else { if err := validateTweak(tweak, TweakSize); err != nil { return nil, err } t = tweak } encrypted, err := KiasuBCEncrypt(key, t, ipBytes) if err != nil { return nil, err } result := make([]byte, TweakSize+16) copy(result[:TweakSize], t) copy(result[TweakSize:], encrypted) return result, nil } // DecryptIPNonDeterministic decrypts an IP address that was encrypted using ipcrypt-nd mode. // The key must be exactly KeySizeND bytes long. // Returns the decrypted IP address as a string. func DecryptIPNonDeterministic(ciphertext []byte, key []byte) (string, error) { if err := validateKey(key, KeySizeND); err != nil { return "", err } if len(ciphertext) != TweakSize+16 { return "", fmt.Errorf("invalid ciphertext length: got %d, want %d", len(ciphertext), TweakSize+16) } tweak := ciphertext[:TweakSize] encryptedIP := ciphertext[TweakSize:] decrypted, err := KiasuBCDecrypt(key, tweak, encryptedIP) if err != nil { return "", err } return net.IP(decrypted).String(), nil } // Prefix-preserving mode functions // EncryptIPPfx encrypts an IP address using ipcrypt-pfx mode. // The key must be exactly 32 bytes long (split into two AES-128 keys). // Returns the encrypted IP address maintaining the original format (IPv4 or IPv6). func EncryptIPPfx(ip net.IP, key []byte) (net.IP, error) { if len(key) != 32 { return nil, fmt.Errorf("%w: got %d bytes, want 32 bytes", ErrInvalidKeySize, len(key)) } // Split the key into two AES-128 keys k1 := key[:16] k2 := key[16:32] // Check that K1 and K2 are different if subtle.ConstantTimeCompare(k1, k2) == 1 { return nil, errors.New("the two halves of the key must be different") } // Convert IP to 16-byte representation ipBytes, err := validateIP(ip) if err != nil { return nil, err } // Create AES cipher objects cipher1, err := aes.NewCipher(k1) if err != nil { return nil, fmt.Errorf("failed to create first cipher: %w", err) } cipher2, err := aes.NewCipher(k2) if err != nil { return nil, fmt.Errorf("failed to create second cipher: %w", err) } // Determine if this is IPv4 isIPv4 := ip.To4() != nil // Initialize encrypted result encrypted := make([]byte, 16) // Determine starting point prefixStart := 0 if isIPv4 { prefixStart = 96 // Copy the IPv4-mapped prefix copy(encrypted[:12], ipBytes[:12]) } // Initialize padded prefix for the starting prefix length paddedPrefix := make([]byte, 16) if isIPv4 { // For IPv4: pad_prefix_96 paddedPrefix[3] = 0x01 // Set bit at position 96 paddedPrefix[14] = 0xFF paddedPrefix[15] = 0xFF } else { // For IPv6: pad_prefix_0 paddedPrefix[15] = 0x01 // Set bit at position 0 } // Process each bit position for prefixLenBits := prefixStart; prefixLenBits < 128; prefixLenBits++ { // Compute pseudorandom function with dual AES encryption e1 := make([]byte, 16) cipher1.Encrypt(e1, paddedPrefix) e2 := make([]byte, 16) cipher2.Encrypt(e2, paddedPrefix) // XOR the two encryptions e := xorBytes(e1, e2) // We only need the least significant bit cipherBit := e[15] & 1 // Extract the current bit from the original IP currentBitPos := 127 - prefixLenBits originalBit := getBit(ipBytes, currentBitPos) // Set the bit in the encrypted result setBit(encrypted, currentBitPos, cipherBit^originalBit) // Prepare padded_prefix for next iteration // Shift left by 1 bit and insert the next bit from ipBytes paddedPrefix = shiftLeftOneBit(paddedPrefix) bitToInsert := getBit(ipBytes, 127-prefixLenBits) setBit(paddedPrefix, 0, bitToInsert) } // Return the appropriate format if isIPv4 { // Return just the IPv4 part return net.IP(encrypted[12:16]), nil } return net.IP(encrypted), nil } // DecryptIPPfx decrypts an IP address that was encrypted using ipcrypt-pfx mode. // The key must be exactly 32 bytes long (split into two AES-128 keys). // Returns the decrypted IP address. func DecryptIPPfx(encryptedIP net.IP, key []byte) (net.IP, error) { if len(key) != 32 { return nil, fmt.Errorf("%w: got %d bytes, want 32 bytes", ErrInvalidKeySize, len(key)) } // Split the key into two AES-128 keys k1 := key[:16] k2 := key[16:32] // Check that K1 and K2 are different if subtle.ConstantTimeCompare(k1, k2) == 1 { return nil, errors.New("the two halves of the key must be different") } // Determine if this is IPv4 isIPv4 := encryptedIP.To4() != nil // Convert to 16-byte representation var encryptedBytes []byte if isIPv4 { // Convert IPv4 to IPv4-mapped IPv6 format encryptedBytes = make([]byte, 16) copy(encryptedBytes[:10], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) copy(encryptedBytes[10:12], []byte{0xff, 0xff}) copy(encryptedBytes[12:], encryptedIP.To4()) } else { var err error encryptedBytes, err = validateIP(encryptedIP) if err != nil { return nil, err } } // Create AES cipher objects cipher1, err := aes.NewCipher(k1) if err != nil { return nil, fmt.Errorf("failed to create first cipher: %w", err) } cipher2, err := aes.NewCipher(k2) if err != nil { return nil, fmt.Errorf("failed to create second cipher: %w", err) } // Initialize decrypted result decrypted := make([]byte, 16) // Determine starting point prefixStart := 0 if isIPv4 { prefixStart = 96 // Copy the IPv4-mapped prefix copy(decrypted[:12], encryptedBytes[:12]) } // Initialize padded prefix for the starting prefix length paddedPrefix := make([]byte, 16) if isIPv4 { // For IPv4: pad_prefix_96 paddedPrefix[3] = 0x01 // Set bit at position 96 paddedPrefix[14] = 0xFF paddedPrefix[15] = 0xFF } else { // For IPv6: pad_prefix_0 paddedPrefix[15] = 0x01 // Set bit at position 0 } // Process each bit position for prefixLenBits := prefixStart; prefixLenBits < 128; prefixLenBits++ { // Compute pseudorandom function with dual AES encryption e1 := make([]byte, 16) cipher1.Encrypt(e1, paddedPrefix) e2 := make([]byte, 16) cipher2.Encrypt(e2, paddedPrefix) // XOR the two encryptions e := xorBytes(e1, e2) // We only need the least significant bit cipherBit := e[15] & 1 // Extract the current bit from the encrypted IP currentBitPos := 127 - prefixLenBits encryptedBit := getBit(encryptedBytes, currentBitPos) // Set the bit in the decrypted result setBit(decrypted, currentBitPos, cipherBit^encryptedBit) // Prepare padded_prefix for next iteration // Shift left by 1 bit and insert the next bit from decrypted paddedPrefix = shiftLeftOneBit(paddedPrefix) bitToInsert := getBit(decrypted, 127-prefixLenBits) setBit(paddedPrefix, 0, bitToInsert) } // Return the appropriate format if isIPv4 { // Return just the IPv4 part return net.IP(decrypted[12:16]), nil } return net.IP(decrypted), nil } // Helper functions for bit manipulation // getBit extracts bit at position from 16-byte array // position: 0 = LSB of byte 15, 127 = MSB of byte 0 func getBit(data []byte, position int) byte { byteIndex := 15 - (position / 8) bitIndex := position % 8 return (data[byteIndex] >> bitIndex) & 1 } // setBit sets bit at position in 16-byte array // position: 0 = LSB of byte 15, 127 = MSB of byte 0 func setBit(data []byte, position int, value byte) { byteIndex := 15 - (position / 8) bitIndex := position % 8 if value != 0 { data[byteIndex] |= 1 << bitIndex } else { data[byteIndex] &^= 1 << bitIndex } } // shiftLeftOneBit shifts a 16-byte array one bit to the left // The most significant bit is lost, and a zero bit is shifted in from the right func shiftLeftOneBit(data []byte) []byte { if len(data) != 16 { return nil } result := make([]byte, 16) carry := byte(0) // Process from least significant byte (byte 15) to most significant (byte 0) for i := 15; i >= 0; i-- { // Current byte shifted left by 1, with carry from previous byte result[i] = ((data[i] << 1) | carry) & 0xFF // Extract the bit that will be carried to the next byte carry = (data[i] >> 7) & 1 } return result } // Extended non-deterministic mode functions // EncryptIPNonDeterministicX encrypts an IP address using ipcrypt-ndx mode. // The key must be exactly KeySizeNDX bytes long. // If tweak is nil, a random tweak will be generated. // Returns a byte slice containing the tweak concatenated with the encrypted IP. func EncryptIPNonDeterministicX(ip string, key []byte, tweak []byte) ([]byte, error) { if err := validateKey(key, KeySizeNDX); err != nil { return nil, err } ipBytes, err := validateIP(net.ParseIP(ip)) if err != nil { return nil, err } key1 := key[:KeySizeND] key2 := key[KeySizeND:] block1, err := aes.NewCipher(key1) if err != nil { return nil, fmt.Errorf("failed to create first cipher: %w", err) } block2, err := aes.NewCipher(key2) if err != nil { return nil, fmt.Errorf("failed to create second cipher: %w", err) } var t []byte if tweak == nil { t = make([]byte, TweakSizeX) if _, err := rand.Read(t); err != nil { return nil, fmt.Errorf("failed to generate tweak: %w", err) } } else { if err := validateTweak(tweak, TweakSizeX); err != nil { return nil, err } t = tweak } encryptedTweak := make([]byte, 16) block2.Encrypt(encryptedTweak, t) xoredIP := xorBytes(ipBytes, encryptedTweak) if xoredIP == nil { return nil, errors.New("XOR operation failed") } encrypted := make([]byte, 16) block1.Encrypt(encrypted, xoredIP) finalEncrypted := xorBytes(encrypted, encryptedTweak) if finalEncrypted == nil { return nil, errors.New("XOR operation failed") } result := make([]byte, TweakSizeX+16) copy(result[:TweakSizeX], t) copy(result[TweakSizeX:], finalEncrypted) return result, nil } // DecryptIPNonDeterministicX decrypts an IP address that was encrypted using ipcrypt-ndx mode. // The key must be exactly KeySizeNDX bytes long. // Returns the decrypted IP address as a string. func DecryptIPNonDeterministicX(ciphertext []byte, key []byte) (string, error) { if err := validateKey(key, KeySizeNDX); err != nil { return "", err } if len(ciphertext) != TweakSizeX+16 { return "", fmt.Errorf("invalid ciphertext length: got %d, want %d", len(ciphertext), TweakSizeX+16) } key1 := key[:KeySizeND] key2 := key[KeySizeND:] block1, err := aes.NewCipher(key1) if err != nil { return "", fmt.Errorf("failed to create first cipher: %w", err) } block2, err := aes.NewCipher(key2) if err != nil { return "", fmt.Errorf("failed to create second cipher: %w", err) } tweak := ciphertext[:TweakSizeX] encryptedIP := ciphertext[TweakSizeX:] encryptedTweak := make([]byte, 16) block2.Encrypt(encryptedTweak, tweak) xoredIP := xorBytes(encryptedIP, encryptedTweak) if xoredIP == nil { return "", errors.New("XOR operation failed") } decrypted := make([]byte, 16) block1.Decrypt(decrypted, xoredIP) finalDecrypted := xorBytes(decrypted, encryptedTweak) if finalDecrypted == nil { return "", errors.New("XOR operation failed") } return net.IP(finalDecrypted).String(), nil } golang-github-jedisct1-go-ipcrypt-0.1.1/ipcrypt_test.go000066400000000000000000000345461506612057700231440ustar00rootroot00000000000000// Package ipcrypt contains tests for the ipcrypt package. package ipcrypt import ( "crypto/rand" "encoding/hex" "errors" "fmt" "net" "strings" "testing" ) // testVector represents a single test case for IP encryption/decryption. type testVector struct { variant string key string ip string tweak string output string } var testVectors = []testVector{ // ipcrypt-deterministic test vectors { variant: "ipcrypt-deterministic", key: "0123456789abcdeffedcba9876543210", ip: "0.0.0.0", output: "bde9:6789:d353:824c:d7c6:f58a:6bd2:26eb", }, { variant: "ipcrypt-deterministic", key: "1032547698badcfeefcdab8967452301", ip: "255.255.255.255", output: "aed2:92f6:ea23:58c3:48fd:8b8:74e8:45d8", }, { variant: "ipcrypt-deterministic", key: "2b7e151628aed2a6abf7158809cf4f3c", ip: "192.0.2.1", output: "1dbd:c1b9:fff1:7586:7d0b:67b4:e76e:4777", }, // ipcrypt-nd test vectors { variant: "ipcrypt-nd", key: "0123456789abcdeffedcba9876543210", ip: "0.0.0.0", tweak: "08e0c289bff23b7c", output: "08e0c289bff23b7cb349aadfe3bcef56221c384c7c217b16", }, { variant: "ipcrypt-nd", key: "1032547698badcfeefcdab8967452301", ip: "192.0.2.1", tweak: "21bd1834bc088cd2", output: "21bd1834bc088cd2e5e1fe55f95876e639faae2594a0caad", }, { variant: "ipcrypt-nd", key: "2b7e151628aed2a6abf7158809cf4f3c", ip: "2001:db8::1", tweak: "b4ecbe30b70898d7", output: "b4ecbe30b70898d7553ac8974d1b4250eafc4b0aa1f80c96", }, // ipcrypt-ndx test vectors { variant: "ipcrypt-ndx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "0.0.0.0", tweak: "21bd1834bc088cd2b4ecbe30b70898d7", output: "21bd1834bc088cd2b4ecbe30b70898d782db0d4125fdace61db35b8339f20ee5", }, { variant: "ipcrypt-ndx", key: "1032547698badcfeefcdab89674523010123456789abcdeffedcba9876543210", ip: "192.0.2.1", tweak: "08e0c289bff23b7cb4ecbe30b70898d7", output: "08e0c289bff23b7cb4ecbe30b70898d7766a533392a69edf1ad0d3ce362ba98a", }, { variant: "ipcrypt-ndx", key: "2b7e151628aed2a6abf7158809cf4f3c3c4fcf098815f7aba6d2ae2816157e2b", ip: "2001:db8::1", tweak: "21bd1834bc088cd2b4ecbe30b70898d7", output: "21bd1834bc088cd2b4ecbe30b70898d76089c7e05ae30c2d10ca149870a263e4", }, // ipcrypt-pfx test vectors { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "0.0.0.0", output: "151.82.155.134", }, { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "255.255.255.255", output: "94.185.169.89", }, { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "192.0.2.1", output: "100.115.72.131", }, { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "2001:db8::1", output: "c180:5dd4:2587:3524:30ab:fa65:6ab6:f88", }, { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "2001:db8::2", output: "c180:5dd4:2587:3524:30ab:fa65:6ab6:f8a", }, { variant: "ipcrypt-pfx", key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "2001:db8:1234:5678:9abc:def0:1234:5678", output: "c180:5dd4:3f39:5792:2334:a348:e913:4af5", }, } // TestReferenceVectors tests all reference vectors for correctness. func TestReferenceVectors(t *testing.T) { for _, tv := range testVectors { t.Run(tv.variant+"/"+tv.ip, func(t *testing.T) { // Parse input IP ip := net.ParseIP(tv.ip) if ip == nil { t.Fatalf("Invalid IP address: %s", tv.ip) } // Parse key key, err := hex.DecodeString(tv.key) if err != nil { t.Fatalf("Failed to decode key: %v", err) } // Variables for encryption/decryption var encrypted []byte var tweak []byte // Test encryption switch tv.variant { case "ipcrypt-deterministic": encryptedIP, err := EncryptIP(key, ip) if err != nil { t.Fatalf("Encryption failed: %v", err) } encrypted = encryptedIP case "ipcrypt-nd": tweak, err = hex.DecodeString(tv.tweak) if err != nil { t.Fatalf("Failed to decode tweak: %v", err) } encrypted, err = EncryptIPNonDeterministic(ip.String(), key, tweak) case "ipcrypt-ndx": tweak, err = hex.DecodeString(tv.tweak) if err != nil { t.Fatalf("Failed to decode tweak: %v", err) } encrypted, err = EncryptIPNonDeterministicX(ip.String(), key, tweak) case "ipcrypt-pfx": encryptedIP, err := EncryptIPPfx(ip, key) if err != nil { t.Fatalf("Encryption failed: %v", err) } encrypted = encryptedIP } if err != nil { t.Fatalf("Encryption failed: %v", err) } // Compare output var got string switch tv.variant { case "ipcrypt-deterministic", "ipcrypt-pfx": got = net.IP(encrypted).String() case "ipcrypt-nd", "ipcrypt-ndx": got = hex.EncodeToString(encrypted) } if got != tv.output { t.Errorf("Encryption mismatch:\nGot: %s\nWant: %s", got, tv.output) } // Test decryption var decrypted net.IP switch tv.variant { case "ipcrypt-deterministic": decrypted, err = DecryptIP(key, net.IP(encrypted)) case "ipcrypt-nd": decryptedStr, err := DecryptIPNonDeterministic(encrypted, key) if err != nil { t.Fatalf("Decryption failed: %v", err) } decrypted = net.ParseIP(decryptedStr) case "ipcrypt-ndx": decryptedStr, err := DecryptIPNonDeterministicX(encrypted, key) if err != nil { t.Fatalf("Decryption failed: %v", err) } decrypted = net.ParseIP(decryptedStr) case "ipcrypt-pfx": decrypted, err = DecryptIPPfx(net.IP(encrypted), key) } if err != nil { t.Fatalf("Decryption failed: %v", err) } // Compare decrypted IP if !decrypted.Equal(ip) { t.Errorf("Decryption mismatch:\nGot: %s\nWant: %s", decrypted, ip) } }) } } func TestIPDeterministic(t *testing.T) { tests := []struct { key string // hex-encoded key ip string expected string }{ { key: "0123456789abcdeffedcba9876543210", ip: "0.0.0.0", expected: "bde9:6789:d353:824c:d7c6:f58a:6bd2:26eb", }, { key: "1032547698badcfeefcdab8967452301", ip: "255.255.255.255", expected: "aed2:92f6:ea23:58c3:48fd:8b8:74e8:45d8", }, { key: "2b7e151628aed2a6abf7158809cf4f3c", ip: "192.0.2.1", expected: "1dbd:c1b9:fff1:7586:7d0b:67b4:e76e:4777", }, } for i, test := range tests { key, err := hex.DecodeString(test.key) if err != nil { t.Errorf("Test %d: Failed to decode key: %v", i, err) continue } ip := net.ParseIP(test.ip) if ip == nil { t.Errorf("Test %d: Invalid IP address: %s", i, test.ip) continue } encrypted, err := EncryptIP(key, ip) if err != nil { t.Errorf("Test %d: Encryption failed: %v", i, err) continue } got := encrypted.String() if got != test.expected { t.Errorf("Test %d: Encryption failed: got %s, want %s", i, got, test.expected) continue } decrypted, err := DecryptIP(key, encrypted) if err != nil { t.Errorf("Test %d: Decryption failed: %v", i, err) continue } if !decrypted.Equal(ip) { t.Errorf("Test %d: Decryption failed: got %s, want %s", i, decrypted, ip) } } } func TestIPNonDeterministic(t *testing.T) { tests := []struct { key string // hex-encoded key ip string tweak string // hex-encoded tweak expected string // hex-encoded expected output (tweak + encrypted IP) }{ { key: "0123456789abcdeffedcba9876543210", ip: "0.0.0.0", tweak: "08e0c289bff23b7c", expected: "08e0c289bff23b7cb349aadfe3bcef56221c384c7c217b16", }, { key: "1032547698badcfeefcdab8967452301", ip: "192.0.2.1", tweak: "21bd1834bc088cd2", expected: "21bd1834bc088cd2e5e1fe55f95876e639faae2594a0caad", }, { key: "2b7e151628aed2a6abf7158809cf4f3c", ip: "2001:db8::1", tweak: "b4ecbe30b70898d7", expected: "b4ecbe30b70898d7553ac8974d1b4250eafc4b0aa1f80c96", }, } for i, test := range tests { key, err := hex.DecodeString(test.key) if err != nil { t.Errorf("Test %d: Failed to decode key: %v", i, err) continue } // Use only first 16 bytes of the key if len(key) > KeySizeND { key = key[:KeySizeND] } tweak, err := hex.DecodeString(test.tweak) if err != nil { t.Errorf("Test %d: Failed to decode tweak: %v", i, err) continue } encrypted, err := EncryptIPNonDeterministic(test.ip, key, tweak) if err != nil { t.Errorf("Test %d: Encryption failed: %v", i, err) continue } // Convert encrypted data to hex string for comparison encryptedHex := hex.EncodeToString(encrypted) if encryptedHex != test.expected { t.Errorf("Test %d: Encryption failed: got %s, want %s", i, encryptedHex, test.expected) continue } decrypted, err := DecryptIPNonDeterministic(encrypted, key) if err != nil { t.Errorf("Test %d: Decryption failed: %v", i, err) continue } if decrypted != test.ip { t.Errorf("Test %d: Decryption failed: got %s, want %s", i, decrypted, test.ip) } } // Test with random tweak key := make([]byte, KeySizeND) if _, err := rand.Read(key); err != nil { t.Fatalf("Failed to generate random key: %v", err) } encrypted, err := EncryptIPNonDeterministic("192.168.1.1", key, nil) if err != nil { t.Fatalf("Failed to encrypt with random tweak: %v", err) } decrypted, err := DecryptIPNonDeterministic(encrypted, key) if err != nil { t.Fatalf("Failed to decrypt with random tweak: %v", err) } if decrypted != "192.168.1.1" { t.Errorf("Random tweak test failed: got %s, want %s", decrypted, "192.168.1.1") } } func TestIPNonDeterministicX(t *testing.T) { tests := []struct { key string // hex-encoded key ip string tweak string // hex-encoded tweak expected string // hex-encoded expected output (tweak + encrypted IP) }{ { key: "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip: "0.0.0.0", tweak: "21bd1834bc088cd2b4ecbe30b70898d7", expected: "21bd1834bc088cd2b4ecbe30b70898d782db0d4125fdace61db35b8339f20ee5", }, { key: "1032547698badcfeefcdab89674523010123456789abcdeffedcba9876543210", ip: "192.0.2.1", tweak: "08e0c289bff23b7cb4ecbe30b70898d7", expected: "08e0c289bff23b7cb4ecbe30b70898d7766a533392a69edf1ad0d3ce362ba98a", }, { key: "2b7e151628aed2a6abf7158809cf4f3c3c4fcf098815f7aba6d2ae2816157e2b", ip: "2001:db8::1", tweak: "21bd1834bc088cd2b4ecbe30b70898d7", expected: "21bd1834bc088cd2b4ecbe30b70898d76089c7e05ae30c2d10ca149870a263e4", }, } for i, test := range tests { key, err := hex.DecodeString(test.key) if err != nil { t.Errorf("Test %d: Failed to decode key: %v", i, err) continue } tweak, err := hex.DecodeString(test.tweak) if err != nil { t.Errorf("Test %d: Failed to decode tweak: %v", i, err) continue } encrypted, err := EncryptIPNonDeterministicX(test.ip, key, tweak) if err != nil { t.Errorf("Test %d: Encryption failed: %v", i, err) continue } // Convert encrypted data to hex string for comparison encryptedHex := hex.EncodeToString(encrypted) if encryptedHex != test.expected { t.Errorf("Test %d: Encryption failed: got %s, want %s", i, encryptedHex, test.expected) continue } decrypted, err := DecryptIPNonDeterministicX(encrypted, key) if err != nil { t.Errorf("Test %d: Decryption failed: %v", i, err) continue } if decrypted != test.ip { t.Errorf("Test %d: Decryption failed: got %s, want %s", i, decrypted, test.ip) } } // Test with random tweak key := make([]byte, KeySizeNDX) if _, err := rand.Read(key); err != nil { t.Fatalf("Failed to generate random key: %v", err) } encrypted, err := EncryptIPNonDeterministicX("192.168.1.1", key, nil) if err != nil { t.Fatalf("Failed to encrypt with random tweak: %v", err) } decrypted, err := DecryptIPNonDeterministicX(encrypted, key) if err != nil { t.Fatalf("Failed to decrypt with random tweak: %v", err) } if decrypted != "192.168.1.1" { t.Errorf("Random tweak test failed: got %s, want %s", decrypted, "192.168.1.1") } } // generateRandomIP generates a random IPv4 address. func generateRandomIP(t *testing.T) string { t.Helper() b := make([]byte, 4) if _, err := rand.Read(b); err != nil { t.Fatalf("Failed to generate random bytes: %v", err) } return fmt.Sprintf("%d.%d.%d.%d", b[0], b[1], b[2], b[3]) } // TestRandomIPs tests encryption and decryption with random IP addresses. func TestRandomIPs(t *testing.T) { key := make([]byte, KeySizeDeterministic) if _, err := rand.Read(key); err != nil { t.Fatalf("Failed to generate random key: %v", err) } const numTests = 100 for i := 0; i < numTests; i++ { ip := generateRandomIP(t) ipAddr := net.ParseIP(ip) if ipAddr == nil { t.Errorf("Invalid IP address: %s", ip) continue } encrypted, err := EncryptIP(key, ipAddr) if err != nil { t.Errorf("EncryptIP failed for IP %s: %v", ip, err) continue } decrypted, err := DecryptIP(key, encrypted) if err != nil { t.Errorf("DecryptIP failed for encrypted IP %s: %v", encrypted, err) continue } if !decrypted.Equal(ipAddr) { t.Errorf("Decryption failed: got %s, want %s", decrypted, ipAddr) } } } // TestInvalidInputs tests error handling for invalid inputs. func TestInvalidInputs(t *testing.T) { // Test invalid key length _, err := EncryptIP([]byte("short"), net.ParseIP("192.168.1.1")) if err == nil || !errors.Is(err, ErrInvalidKeySize) { t.Errorf("Expected key length error, got %v", err) } // Test invalid IP address _, err = EncryptIP(make([]byte, KeySizeDeterministic), net.ParseIP("not-an-ip")) if err == nil || !errors.Is(err, ErrInvalidIP) { t.Errorf("Expected invalid IP error, got %v", err) } // Test invalid ciphertext length for non-deterministic mode _, err = DecryptIPNonDeterministic([]byte("short"), make([]byte, KeySizeND)) if err == nil || !strings.Contains(err.Error(), "invalid ciphertext length") { t.Errorf("Expected invalid ciphertext length error, got %v", err) } // Test invalid ciphertext length for non-deterministic X mode _, err = DecryptIPNonDeterministicX([]byte("short"), make([]byte, KeySizeNDX)) if err == nil || !strings.Contains(err.Error(), "invalid ciphertext length") { t.Errorf("Expected invalid ciphertext length error, got %v", err) } } golang-github-jedisct1-go-ipcrypt-0.1.1/kiasu-bc.go000066400000000000000000000313221506612057700220760ustar00rootroot00000000000000package ipcrypt import ( "errors" ) // AES round constants var rcon = [10]byte{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, } // Precomputed tables for multiplication by 2 and 3 in GF(2^8) var ( gmul2 = [256]byte{ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5, } gmul3 = [256]byte{ 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a, } ) // AES S-box var sbox = [256]byte{ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, } // AES inverse S-box var invSbox = [256]byte{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, } // gmul performs multiplication in GF(2^8) func gmul(a, b byte) byte { var p byte for i := 0; i < 8; i++ { if (b & 1) != 0 { p ^= a } highBitSet := (a & 0x80) != 0 a <<= 1 if highBitSet { a ^= 0x1b } b >>= 1 } return p } // shiftRows performs the ShiftRows operation on the state func shiftRows(state []byte) { // Row 0: no shift // Row 1: shift left by 1 state[1], state[5], state[9], state[13] = state[5], state[9], state[13], state[1] // Row 2: shift left by 2 state[2], state[6], state[10], state[14] = state[10], state[14], state[2], state[6] // Row 3: shift left by 3 state[3], state[7], state[11], state[15] = state[15], state[3], state[7], state[11] } // invShiftRows performs the inverse ShiftRows operation on the state func invShiftRows(state []byte) { // Row 0: no shift // Row 1: shift right by 1 state[1], state[5], state[9], state[13] = state[13], state[1], state[5], state[9] // Row 2: shift right by 2 state[2], state[6], state[10], state[14] = state[10], state[14], state[2], state[6] // Row 3: shift right by 3 state[3], state[7], state[11], state[15] = state[7], state[11], state[15], state[3] } // mixColumns performs the MixColumns operation on the state func mixColumns(state []byte) { for i := 0; i < 16; i += 4 { s0 := state[i] s1 := state[i+1] s2 := state[i+2] s3 := state[i+3] state[i] = gmul2[s0] ^ gmul3[s1] ^ s2 ^ s3 state[i+1] = s0 ^ gmul2[s1] ^ gmul3[s2] ^ s3 state[i+2] = s0 ^ s1 ^ gmul2[s2] ^ gmul3[s3] state[i+3] = gmul3[s0] ^ s1 ^ s2 ^ gmul2[s3] } } // invMixColumns performs the inverse MixColumns operation on the state func invMixColumns(state []byte) { for i := 0; i < 16; i += 4 { s0 := state[i] s1 := state[i+1] s2 := state[i+2] s3 := state[i+3] state[i] = gmul(0x0e, s0) ^ gmul(0x0b, s1) ^ gmul(0x0d, s2) ^ gmul(0x09, s3) state[i+1] = gmul(0x09, s0) ^ gmul(0x0e, s1) ^ gmul(0x0b, s2) ^ gmul(0x0d, s3) state[i+2] = gmul(0x0d, s0) ^ gmul(0x09, s1) ^ gmul(0x0e, s2) ^ gmul(0x0b, s3) state[i+3] = gmul(0x0b, s0) ^ gmul(0x0d, s1) ^ gmul(0x09, s2) ^ gmul(0x0e, s3) } } // expandKey expands the 16-byte key into 11 round keys using AES key expansion func expandKey(key []byte) [][]byte { if len(key) != 16 { panic("key must be 16 bytes") } // Pre-allocate all memory at once roundKeys := make([][]byte, 11) allKeys := make([]byte, 11*16) for i := range roundKeys { roundKeys[i] = allKeys[i*16 : (i+1)*16] } // Copy initial key copy(roundKeys[0], key) // Single temporary buffer for all rounds temp := make([]byte, 4) for i := 1; i < 11; i++ { prevKey := roundKeys[i-1] currKey := roundKeys[i] // First word // RotWord temp[0] = prevKey[13] temp[1] = prevKey[14] temp[2] = prevKey[15] temp[3] = prevKey[12] // SubWord temp[0] = sbox[temp[0]] temp[1] = sbox[temp[1]] temp[2] = sbox[temp[2]] temp[3] = sbox[temp[3]] // XOR with Rcon temp[0] ^= rcon[i-1] // XOR with previous key currKey[0] = prevKey[0] ^ temp[0] currKey[1] = prevKey[1] ^ temp[1] currKey[2] = prevKey[2] ^ temp[2] currKey[3] = prevKey[3] ^ temp[3] // Remaining words currKey[4] = currKey[0] ^ prevKey[4] currKey[5] = currKey[1] ^ prevKey[5] currKey[6] = currKey[2] ^ prevKey[6] currKey[7] = currKey[3] ^ prevKey[7] currKey[8] = currKey[4] ^ prevKey[8] currKey[9] = currKey[5] ^ prevKey[9] currKey[10] = currKey[6] ^ prevKey[10] currKey[11] = currKey[7] ^ prevKey[11] currKey[12] = currKey[8] ^ prevKey[12] currKey[13] = currKey[9] ^ prevKey[13] currKey[14] = currKey[10] ^ prevKey[14] currKey[15] = currKey[11] ^ prevKey[15] } return roundKeys } // padTweak pads an 8-byte tweak to 16 bytes according to KIASU-BC specification. // The tweak is padded by placing each 2-byte pair at the start of a 4-byte group. func padTweak(tweak []byte) []byte { if len(tweak) != 8 { panic("tweak must be 8 bytes") } padded := make([]byte, 16) for i := 0; i < 8; i += 2 { padded[i*2] = tweak[i] padded[i*2+1] = tweak[i+1] } return padded } // KiasuBCEncrypt encrypts a 16-byte block using KIASU-BC with the given key and tweak. func KiasuBCEncrypt(key, tweak, block []byte) ([]byte, error) { if len(key) != 16 { return nil, errors.New("key must be 16 bytes") } if len(tweak) != 8 { return nil, errors.New("tweak must be 8 bytes") } if len(block) != 16 { return nil, errors.New("block must be 16 bytes") } // Expand key and pad tweak roundKeys := expandKey(key) paddedTweak := padTweak(tweak) // Create state state := make([]byte, 16) copy(state, block) // Initial round for i := 0; i < 16; i++ { state[i] ^= roundKeys[0][i] ^ paddedTweak[i] } // Main rounds for round := 1; round < 10; round++ { // SubBytes for i := 0; i < 16; i++ { state[i] = sbox[state[i]] } // ShiftRows shiftRows(state) // MixColumns mixColumns(state) // AddRoundKey for i := 0; i < 16; i++ { state[i] ^= roundKeys[round][i] ^ paddedTweak[i] } } // Final round // SubBytes for i := 0; i < 16; i++ { state[i] = sbox[state[i]] } // ShiftRows shiftRows(state) // AddRoundKey for i := 0; i < 16; i++ { state[i] ^= roundKeys[10][i] ^ paddedTweak[i] } return state, nil } // KiasuBCDecrypt decrypts a 16-byte block using KIASU-BC with the given key and tweak. func KiasuBCDecrypt(key, tweak, block []byte) ([]byte, error) { if len(key) != 16 { return nil, errors.New("key must be 16 bytes") } if len(tweak) != 8 { return nil, errors.New("tweak must be 8 bytes") } if len(block) != 16 { return nil, errors.New("block must be 16 bytes") } // Expand key and pad tweak roundKeys := expandKey(key) paddedTweak := padTweak(tweak) // Create state state := make([]byte, 16) copy(state, block) // Initial round for i := 0; i < 16; i++ { state[i] ^= roundKeys[10][i] ^ paddedTweak[i] } // Inverse ShiftRows invShiftRows(state) // Inverse SubBytes for i := 0; i < 16; i++ { state[i] = invSbox[state[i]] } // Main rounds for round := 9; round > 0; round-- { // AddRoundKey for i := 0; i < 16; i++ { state[i] ^= roundKeys[round][i] ^ paddedTweak[i] } // Inverse MixColumns invMixColumns(state) // Inverse ShiftRows invShiftRows(state) // Inverse SubBytes for i := 0; i < 16; i++ { state[i] = invSbox[state[i]] } } // Final round // AddRoundKey for i := 0; i < 16; i++ { state[i] ^= roundKeys[0][i] ^ paddedTweak[i] } return state, nil }