package crypt import ( "encoding/base64" "encoding/hex" "errors" "strings" "testing" ) // testKey — 32 байта в base64 (та же строка ниже и в hex). var ( testKeyB64 = base64.StdEncoding.EncodeToString(make([]byte, 32)) testKeyHex = hex.EncodeToString(make([]byte, 32)) ) // TestNewKeyFormat — принимаются base64 и hex, отклоняется прочее: // пустой ключ, короткий, мусор и неверный размер. func TestNewKeyFormat(t *testing.T) { for _, key := range []string{testKeyB64, testKeyHex, strings.TrimRight(testKeyB64, "=")} { if _, err := New(key); err != nil { t.Errorf("New(%q): %v", key[:12], err) } } for _, key := range []string{ "", "short", base64.StdEncoding.EncodeToString(make([]byte, 16)), // 16 байт — AES-128, не наш формат strings.Repeat("z", 44), // base64 от 33 байт } { if _, err := New(key); !errors.Is(err, ErrKeySize) { t.Errorf("New(%q): err = %v, want ErrKeySize", key, err) } } } // TestSealOpen — основной путь: шифрование обратимо, ciphertext не // содержит исходный текст и меняется от прогона к прогону (nonce). func TestSealOpen(t *testing.T) { c, err := New(testKeyB64) if err != nil { t.Fatalf("New: %v", err) } const secret = "123456789:AAFakeBotTokenForTests0123456789" enc, err := c.Seal(secret) if err != nil { t.Fatalf("Seal: %v", err) } if strings.Contains(enc, secret) || strings.Contains(enc, "AAFake") { t.Errorf("ciphertext leaks plaintext: %q", enc) } got, err := c.Open(enc) if err != nil { t.Fatalf("Open: %v", err) } if got != secret { t.Errorf("roundtrip = %q, want %q", got, secret) } // Та же строка шифруется заново: nonce другой, результат другой — // иначе два пользователя с одинаковым токеном дали бы одинаковый blob. again, err := c.Seal(secret) if err != nil { t.Fatalf("Seal again: %v", err) } if again == enc { t.Error("two seals of the same value are equal (nonce reuse?)") } } // TestOpenRejects — битые и чужие данные: обрезанный результат, битый // base64, подделанный байт (GCM поймает), другой ключ. func TestOpenRejects(t *testing.T) { c, err := New(testKeyB64) if err != nil { t.Fatalf("New: %v", err) } other, err := New(hex.EncodeToString(append(make([]byte, 31), 1))) if err != nil { t.Fatalf("New other: %v", err) } enc, err := c.Seal("topsecret") if err != nil { t.Fatalf("Seal: %v", err) } raw, err := base64.StdEncoding.DecodeString(enc) if err != nil { t.Fatalf("decode: %v", err) } raw[len(raw)-1] ^= 0xFF // битый тэг GCM tampered := base64.StdEncoding.EncodeToString(raw) for name, tc := range map[string]struct { c *Cipher in string }{ "not base64": {c, "не base64"}, "too short": {c, base64.StdEncoding.EncodeToString([]byte("abc"))}, "tampered": {c, tampered}, "another key": {other, enc}, "empty": {c, ""}, } { t.Run(name, func(t *testing.T) { if _, err := tc.c.Open(tc.in); !errors.Is(err, ErrCorrupt) { t.Errorf("err = %v, want ErrCorrupt", err) } }) } } // TestSealEmpty — пустая строка шифруется и расшифровывается (нужно, // когда пустое значение хранят сознательно). func TestSealEmpty(t *testing.T) { c, err := New(testKeyHex) if err != nil { t.Fatalf("New: %v", err) } enc, err := c.Seal("") if err != nil { t.Fatalf("Seal: %v", err) } got, err := c.Open(enc) if err != nil || got != "" { t.Errorf("got %q, err %v, want empty", got, err) } }