func TestGCMCounterWrap(t *testing.T) { // Test that the last 32-bits of the counter wrap correctly. tests := []struct { nonce, tag string }{ {"0fa72e25", "37e1948cdfff09fbde0c40ad99fee4a7"}, // counter: 7eb59e4d961dad0dfdd75aaffffffff0 {"afe05cc1", "438f3aa9fee5e54903b1927bca26bbdf"}, // counter: 75d492a7e6e6bfc979ad3a8ffffffff4 {"9ffecbef", "7b88ca424df9703e9e8611071ec7e16e"}, // counter: c8bb108b0ecdc71747b9d57ffffffff5 {"ffc3e5b3", "38d49c86e0abe853ac250e66da54c01a"}, // counter: 706414d2de9b36ab3b900a9ffffffff6 {"cfdd729d", "e08402eaac36a1a402e09b1bd56500e8"}, // counter: cd0b96fe36b04e750584e56ffffffff7 {"010ae3d486", "5405bb490b1f95d01e2ba735687154bc"}, // counter: e36c18e69406c49722808104fffffff8 {"01b1107a9d", "939a585f342e01e17844627492d44dbf"}, // counter: e6d56eaf9127912b6d62c6dcffffffff } key, err := aes.NewCipher(make([]byte, 16)) if err != nil { t.Fatal(err) } plaintext := make([]byte, 16*17+1) for i, test := range tests { nonce, _ := hex.DecodeString(test.nonce) want, _ := hex.DecodeString(test.tag) aead, err := cipher.NewGCMWithNonceSize(key, len(nonce)) if err != nil { t.Fatal(err) } got := aead.Seal(nil, nonce, plaintext, nil) if !bytes.Equal(got[len(plaintext):], want) { t.Errorf("test[%v]: got: %x, want: %x", i, got[len(plaintext):], want) } _, err = aead.Open(nil, nonce, got, nil) if err != nil { t.Errorf("test[%v]: authentication failed", i) } } }
func TestAESGCM(t *testing.T) { for i, test := range aesGCMTests { key, _ := hex.DecodeString(test.key) aes, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } nonce, _ := hex.DecodeString(test.nonce) plaintext, _ := hex.DecodeString(test.plaintext) ad, _ := hex.DecodeString(test.ad) aesgcm, err := cipher.NewGCMWithNonceSize(aes, len(nonce)) if err != nil { t.Fatal(err) } ct := aesgcm.Seal(nil, nonce, plaintext, ad) if ctHex := hex.EncodeToString(ct); ctHex != test.result { t.Errorf("#%d: got %s, want %s", i, ctHex, test.result) continue } plaintext2, err := aesgcm.Open(nil, nonce, ct, ad) if err != nil { t.Errorf("#%d: Open failed", i) continue } if !bytes.Equal(plaintext, plaintext2) { t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext) continue } if len(ad) > 0 { ad[0] ^= 0x80 if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil { t.Errorf("#%d: Open was successful after altering additional data", i) } ad[0] ^= 0x80 } nonce[0] ^= 0x80 if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil { t.Errorf("#%d: Open was successful after altering nonce", i) } nonce[0] ^= 0x80 ct[0] ^= 0x80 if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil { t.Errorf("#%d: Open was successful after altering ciphertext", i) } ct[0] ^= 0x80 } }
// DecryptFile decrypts the file at the specified path using GCM func DecryptFile(inFilePath, outFilePath string, key, givenIV, aad []byte) error { if _, err := os.Stat(inFilePath); os.IsNotExist(err) { return fmt.Errorf("A file does not exist at %s", inFilePath) } // copy the IV since it will potentially be incremented iv := make([]byte, len(givenIV)) copy(iv, givenIV) inFile, err := os.Open(inFilePath) if err != nil { return err } defer inFile.Close() outFile, err := os.OpenFile(outFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return err } defer outFile.Close() aes, err := aes.NewCipher(key) if err != nil { return err } gcm, err := cipher.NewGCMWithNonceSize(aes, len(iv)) if err != nil { return err } written := false chunk := make([]byte, chunkSize+gcm.Overhead()) for { read, err := inFile.Read(chunk) // ensure we have written at least one chunk before breaking if read > 0 || !written { decrChunk, err := gcm.Open(nil, iv, chunk[:read], aad) if err != nil { return err } if _, err := outFile.Write(decrChunk); err != nil { return err } written = true } if err == io.EOF { break } incrementIV(iv) } return nil }
// Encrypt takes one of (string, int, float, bool) and encrypts it with the provided key and additional auth data, returning a sops-format encrypted string. func (c Cipher) Encrypt(value interface{}, key []byte, path string, stash interface{}) (string, error) { if value == "" { return "", nil } aescipher, err := cryptoaes.NewCipher(key) if err != nil { return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err) } var iv []byte if stash, ok := stash.(stashData); !ok || stash.plaintext != value { iv = make([]byte, nonceSize) _, err = rand.Read(iv) if err != nil { return "", fmt.Errorf("Could not generate random bytes for IV: %s", err) } } else { iv = stash.iv } gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize) if err != nil { return "", fmt.Errorf("Could not create GCM: %s", err) } var plaintext []byte var encryptedType string switch value := value.(type) { case string: encryptedType = "str" plaintext = []byte(value) case int: encryptedType = "int" plaintext = []byte(strconv.Itoa(value)) case float64: encryptedType = "float" // The Python version encodes floats without padding 0s after the decimal point. plaintext = []byte(strconv.FormatFloat(value, 'f', -1, 64)) case bool: encryptedType = "bool" // The Python version encodes booleans with Titlecase plaintext = []byte(strings.Title(strconv.FormatBool(value))) default: return "", fmt.Errorf("Value to encrypt has unsupported type %T", value) } out := gcm.Seal(nil, iv, plaintext, []byte(path)) return fmt.Sprintf("ENC[AES256_GCM,data:%s,iv:%s,tag:%s,type:%s]", base64.StdEncoding.EncodeToString(out[:len(out)-cryptoaes.BlockSize]), base64.StdEncoding.EncodeToString(iv), base64.StdEncoding.EncodeToString(out[len(out)-cryptoaes.BlockSize:]), encryptedType), nil }
// Decrypt takes a sops-format value string and a key and returns the decrypted value and a stash value func (c Cipher) Decrypt(value string, key []byte, path string) (plaintext interface{}, stash interface{}, err error) { if value == "" { return "", nil, nil } encryptedValue, err := parse(value) if err != nil { return "", nil, err } aescipher, err := cryptoaes.NewCipher(key) if err != nil { return "", nil, err } gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv)) if err != nil { return "", nil, err } stashValue := stashData{iv: encryptedValue.iv} data := append(encryptedValue.data, encryptedValue.tag...) decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, []byte(path)) if err != nil { return "", nil, fmt.Errorf("Could not decrypt with AES_GCM: %s", err) } decryptedValue := string(decryptedBytes) switch encryptedValue.datatype { case "str": stashValue.plaintext = decryptedValue return decryptedValue, stashValue, nil case "int": plaintext, err = strconv.Atoi(decryptedValue) stashValue.plaintext = plaintext return plaintext, stashValue, err case "float": plaintext, err = strconv.ParseFloat(decryptedValue, 64) stashValue.plaintext = plaintext return plaintext, stashValue, err case "bytes": stashValue.plaintext = decryptedBytes return decryptedBytes, stashValue, nil case "bool": plaintext, err = strconv.ParseBool(decryptedValue) stashValue.plaintext = plaintext return plaintext, stashValue, err default: return nil, nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype) } }
// TestEncryptDecrypt encrypts and decrypts using both stupidgcm and Go's built-in // GCM implemenatation and verifies that the results are identical. func TestEncryptDecrypt(t *testing.T) { key := randBytes(32) sGCM := New(key) authData := randBytes(24) iv := randBytes(16) dst := make([]byte, 71) // 71 = random length gAES, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } gGCM, err := cipher.NewGCMWithNonceSize(gAES, 16) if err != nil { t.Fatal(err) } // Check all block sizes from 1 to 5000 for i := 1; i < 5000; i++ { in := make([]byte, i) sOut := sGCM.Seal(dst, iv, in, authData) gOut := gGCM.Seal(dst, iv, in, authData) // Ciphertext must be identical to Go GCM if !bytes.Equal(sOut, gOut) { t.Fatalf("Compare failed for encryption, size %d", i) t.Log("sOut:") t.Log("\n" + hex.Dump(sOut)) t.Log("gOut:") t.Log("\n" + hex.Dump(gOut)) } sOut2, sErr := sGCM.Open(dst, iv, sOut[len(dst):], authData) if sErr != nil { t.Fatal(sErr) } gOut2, gErr := gGCM.Open(dst, iv, gOut[len(dst):], authData) if gErr != nil { t.Fatal(gErr) } // Plaintext must be identical to Go GCM if !bytes.Equal(sOut2, gOut2) { t.Fatalf("Compare failed for decryption, size %d", i) } } }
func Benchmark4kEncGoGCM(b *testing.B) { key := randBytes(32) authData := randBytes(24) iv := randBytes(16) in := make([]byte, 4096) b.SetBytes(int64(len(in))) gAES, err := aes.NewCipher(key) if err != nil { b.Fatal(err) } gGCM, err := cipher.NewGCMWithNonceSize(gAES, 16) if err != nil { b.Fatal(err) } for i := 0; i < b.N; i++ { // Encrypt and append to nonce gGCM.Seal(iv, iv, in, authData) } }
// Encrypt takes one of (string, int, float, bool) and encrypts it with the provided key and additional auth data, returning a sops-format encrypted string. func (c Cipher) Encrypt(value interface{}, key []byte, additionalAuthData []byte) (string, error) { aescipher, err := cryptoaes.NewCipher(key) if err != nil { return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err) } iv := make([]byte, nonceSize) _, err = rand.Read(iv) if err != nil { return "", fmt.Errorf("Could not generate random bytes for IV: %s", err) } gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize) if err != nil { return "", fmt.Errorf("Could not create GCM: %s", err) } var plaintext []byte var encryptedType string switch value := value.(type) { case string: encryptedType = "str" plaintext = []byte(value) case int: encryptedType = "int" plaintext = []byte(strconv.Itoa(value)) case float64: encryptedType = "float" plaintext = []byte(strconv.FormatFloat(value, 'f', 9, 64)) case bool: encryptedType = "bool" plaintext = []byte(strconv.FormatBool(value)) default: return "", fmt.Errorf("Value to encrypt has unsupported type %T", value) } out := gcm.Seal(nil, iv, plaintext, additionalAuthData) return fmt.Sprintf("ENC[AES256_GCM,data:%s,iv:%s,tag:%s,type:%s]", base64.StdEncoding.EncodeToString(out[:len(out)-cryptoaes.BlockSize]), base64.StdEncoding.EncodeToString(iv), base64.StdEncoding.EncodeToString(out[len(out)-cryptoaes.BlockSize:]), encryptedType), nil }
// Decrypt takes a sops-format value string and a key and returns the decrypted value. func (c Cipher) Decrypt(value string, key []byte, additionalAuthData []byte) (interface{}, error) { encryptedValue, err := parse(value) if err != nil { return "", err } aescipher, err := cryptoaes.NewCipher(key) if err != nil { return "", err } gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv)) if err != nil { return "", err } data := append(encryptedValue.data, encryptedValue.tag...) decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, additionalAuthData) if err != nil { return "", fmt.Errorf("Could not decrypt with AES_GCM: %s", err) } decryptedValue := string(decryptedBytes) switch encryptedValue.datatype { case "str": return decryptedValue, nil case "int": return strconv.Atoi(decryptedValue) case "float": return strconv.ParseFloat(decryptedValue, 64) case "bytes": return decryptedValue, nil case "bool": return strconv.ParseBool(decryptedValue) default: return nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype) } }
// goGCMWrapper - This wrapper makes sure gocryptfs can be compiled on Go // versions 1.4 and lower that lack NewGCMWithNonceSize(). // 128 bit GCM IVs will not work when using built-in Go crypto, obviously, when // compiled on 1.4. func goGCMWrapper(bc cipher.Block, nonceSize int) (cipher.AEAD, error) { return cipher.NewGCMWithNonceSize(bc, nonceSize) }