├── README.md ├── ipcrypt.go ├── ipcrypt.py └── test.csv /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ipcrypt: IP-format-preserving encryption 3 | 4 | Encrypts an IPv4 address to another IPv4 address 5 | 6 | Can be used to "anonymize" logs, etc. 7 | 8 | Takes as argument a CSV file and the index of a field containing IP 9 | addresses 10 | 11 | Uses a custom 4-byte-block cipher, inspired from SipHash. 12 | 13 | Set the variable `KEY` to a 16-byte secret key 14 | 15 | Example use, with the default key `some 16-byte key`: 16 | ``` 17 | $ cat test.csv 18 | a,127.0.0.1 19 | b,8.8.8.8 20 | c,1.2.3.4 21 | $ python ipcrypt.py test.csv 1 e 22 | a,114.62.227.59 23 | b,46.48.51.50 24 | c,171.238.15.199 25 | $ python ipcrypt.py test.csv 1 e > tmp 26 | $ python ipcrypt.py tmp 1 d 27 | a,127.0.0.1 28 | b,8.8.8.8 29 | c,1.2.3.4 30 | ``` 31 | 32 | The Go version has a similar syntax; for example 33 | ``` 34 | $ go build ipcrypt.go 35 | $ ./ipcrypt test.csv 1 e 36 | a,114.62.227.59 37 | b,46.48.51.50 38 | c,171.238.15.199 39 | ``` 40 | 41 | Copyright (c) 2015 Jean-Philippe Aumasson 42 | 43 | Under CC0 license 44 | -------------------------------------------------------------------------------- /ipcrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // see also https://github.com/dgryski/go-ipcrypt/ 4 | 5 | import ( 6 | "encoding/csv" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "os" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | // copy your key here 17 | const ( 18 | KEY = "some 16-byte key" 19 | ) 20 | 21 | func rotl(x, c byte) byte { 22 | return (x << c) | (x >> (8 - c)) 23 | } 24 | 25 | func permute_fwd(state [4]byte) [4]byte { 26 | a := state[0] 27 | b := state[1] 28 | c := state[2] 29 | d := state[3] 30 | a += b 31 | c += d 32 | a &= 0xff 33 | c &= 0xff 34 | b = rotl(b, 2) 35 | d = rotl(d, 5) 36 | b ^= a 37 | d ^= c 38 | a = rotl(a, 4) 39 | a += d 40 | c += b 41 | a &= 0xff 42 | c &= 0xff 43 | b = rotl(b, 3) 44 | d = rotl(d, 7) 45 | b ^= c 46 | d ^= a 47 | c = rotl(c, 4) 48 | return [4]byte{a, b, c, d} 49 | } 50 | 51 | func permute_bwd(state [4]byte) [4]byte { 52 | a := state[0] 53 | b := state[1] 54 | c := state[2] 55 | d := state[3] 56 | c = rotl(c, 4) 57 | b ^= c 58 | d ^= a 59 | b = rotl(b, 5) 60 | d = rotl(d, 1) 61 | a -= d 62 | c -= b 63 | a &= 0xff 64 | c &= 0xff 65 | a = rotl(a, 4) 66 | b ^= a 67 | d ^= c 68 | b = rotl(b, 6) 69 | d = rotl(d, 3) 70 | a -= b 71 | c -= d 72 | a &= 0xff 73 | c &= 0xff 74 | return [4]byte{a, b, c, d} 75 | } 76 | 77 | func xor4(x [4]byte, y []byte) [4]byte { 78 | return [4]byte{x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]} 79 | } 80 | 81 | func bytes2ip(bytes [4]byte) string { 82 | ipaddr := []string{"", "", "", ""} 83 | ipaddr[0] = strconv.Itoa(int(bytes[0])) 84 | ipaddr[1] = strconv.Itoa(int(bytes[1])) 85 | ipaddr[2] = strconv.Itoa(int(bytes[2])) 86 | ipaddr[3] = strconv.Itoa(int(bytes[3])) 87 | return strings.Join(ipaddr, ".") 88 | } 89 | 90 | func Encrypt(k [16]byte, ip string) (string, error) { 91 | p := net.ParseIP(ip) 92 | if p == nil { 93 | return "", errors.New("encrypt: invalid IP") 94 | } 95 | state := [4]byte{p[12], p[13], p[14], p[15]} 96 | 97 | state = xor4(state, k[:4]) 98 | state = permute_fwd(state) 99 | state = xor4(state, k[4:8]) 100 | state = permute_fwd(state) 101 | state = xor4(state, k[8:12]) 102 | state = permute_fwd(state) 103 | state = xor4(state, k[12:16]) 104 | 105 | return bytes2ip(state), nil 106 | } 107 | 108 | func Decrypt(k [16]byte, ip string) (string, error) { 109 | p := net.ParseIP(ip) 110 | if p == nil { 111 | return "", errors.New("encrypt: invalid IP") 112 | } 113 | state := [4]byte{p[12], p[13], p[14], p[15]} 114 | 115 | state = xor4(state, k[12:16]) 116 | state = permute_bwd(state) 117 | state = xor4(state, k[8:12]) 118 | state = permute_bwd(state) 119 | state = xor4(state, k[4:8]) 120 | state = permute_bwd(state) 121 | state = xor4(state, k[:4]) 122 | 123 | return bytes2ip(state), nil 124 | } 125 | 126 | func test() error { 127 | ip := "1.2.3.4" 128 | init := ip 129 | var err error 130 | var key [16]byte 131 | for i := 0; i < 16; i++ { 132 | key[i] = 0xff 133 | } 134 | for i := 0; i < 10; i++ { 135 | ip, err = Encrypt(key, ip) 136 | if err != nil { 137 | return err 138 | } 139 | } 140 | if ip != "191.207.11.210" { 141 | return errors.New("test failed: wrong intermediate value") 142 | } 143 | for i := 0; i < 10; i++ { 144 | ip, err = Decrypt(key, ip) 145 | if err != nil { 146 | return err 147 | } 148 | } 149 | if init != ip { 150 | return errors.New("test failed: decrypted values doesn't match") 151 | } 152 | return nil 153 | } 154 | 155 | func main() { 156 | err := test() 157 | if err != nil { 158 | fmt.Println(err) 159 | return 160 | } 161 | if len(os.Args) < 4 { 162 | fmt.Println("not enough arguments") 163 | return 164 | } 165 | filein := os.Args[1] 166 | index, err := strconv.Atoi(os.Args[2]) 167 | if err != nil { 168 | fmt.Println(err) 169 | return 170 | } 171 | mode := os.Args[3] 172 | 173 | var process func([16]byte, string) (string, error) 174 | if mode == "e" { 175 | process = Encrypt 176 | } else if mode == "d" { 177 | process = Decrypt 178 | } else { 179 | fmt.Println("error: wrong mode") 180 | return 181 | } 182 | 183 | file, err := os.Open(filein) 184 | if err != nil { 185 | fmt.Println(err) 186 | return 187 | } 188 | 189 | defer file.Close() 190 | 191 | reader := csv.NewReader(file) 192 | reader.Comma = ',' 193 | writer := csv.NewWriter(os.Stdout) 194 | writer.Comma = ',' 195 | 196 | var key [16]byte 197 | for i := 0; i < 16; i++ { 198 | key[i] = byte(KEY[i]) 199 | } 200 | 201 | for { 202 | line, err := reader.Read() 203 | if err == io.EOF { 204 | break 205 | } else if err != nil { 206 | fmt.Println(err) 207 | return 208 | } 209 | newline := line 210 | newline[index], err = process(key, line[index]) 211 | if err != nil { 212 | fmt.Println(err) 213 | return 214 | } 215 | if err == nil { 216 | writer.Write(newline) 217 | } 218 | } 219 | 220 | writer.Flush() 221 | } 222 | -------------------------------------------------------------------------------- /ipcrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import csv 4 | import struct 5 | import sys 6 | 7 | # copy your key here 8 | KEY = 'some 16-byte key' 9 | # CSV delimiter character 10 | DELIMITER = ',' 11 | 12 | 13 | def rotl(b, r): 14 | return ((b << r) & 0xff) | (b >> (8 - r)) 15 | 16 | 17 | def permute_fwd(state): 18 | (b0, b1, b2, b3) = state 19 | b0 += b1 20 | b2 += b3 21 | b0 &= 0xff 22 | b2 &= 0xff 23 | b1 = rotl(b1, 2) 24 | b3 = rotl(b3, 5) 25 | b1 ^= b0 26 | b3 ^= b2 27 | b0 = rotl(b0, 4) 28 | b0 += b3 29 | b2 += b1 30 | b0 &= 0xff 31 | b2 &= 0xff 32 | b1 = rotl(b1, 3) 33 | b3 = rotl(b3, 7) 34 | b1 ^= b2 35 | b3 ^= b0 36 | b2 = rotl(b2, 4) 37 | return (b0, b1, b2, b3) 38 | 39 | 40 | def permute_bwd(state): 41 | (b0, b1, b2, b3) = state 42 | b2 = rotl(b2, 4) 43 | b1 ^= b2 44 | b3 ^= b0 45 | b1 = rotl(b1, 5) 46 | b3 = rotl(b3, 1) 47 | b0 -= b3 48 | b2 -= b1 49 | b0 &= 0xff 50 | b2 &= 0xff 51 | b0 = rotl(b0, 4) 52 | b1 ^= b0 53 | b3 ^= b2 54 | b1 = rotl(b1, 6) 55 | b3 = rotl(b3, 3) 56 | b0 -= b1 57 | b2 -= b3 58 | b0 &= 0xff 59 | b2 &= 0xff 60 | return (b0, b1, b2, b3) 61 | 62 | 63 | def xor4(x, y): 64 | return [(x[i] ^ y[i]) & 0xff for i in (0, 1, 2, 3)] 65 | 66 | 67 | def encrypt(key, ip): 68 | """16-byte key, ip string like '192.168.1.2'""" 69 | k = [struct.unpack('