diff --git a/README.md b/README.md index c92c1b6..5bc0f0a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ hashing algorithms to Go and to provide a simple and consistent interface to each of them. As every hashing method is implemented in pure Go, this library should be as portable as Go itself. +Supported Algorithms: +- Traditional DES (13-character standard UNIX crypt(3)) +- MD5-crypt ($1$) +- Apache APR1 ($apr1$) +- SHA-256-crypt ($5$) +- SHA-512-crypt ($6$) + All hashing methods come with a test suite which verifies their operation against itself as well as the output of other password hashing implementations to ensure compatibility with them. @@ -20,6 +27,37 @@ I hope you find this library to be useful and easy to use! go get github.com/tredoe/crypt@latest +## Usage + +```go +package main + +import ( + "fmt" + + "github.com/tredoe/crypt" + _ "github.com/tredoe/crypt/sha256_crypt" + _ "github.com/tredoe/crypt/des_crypt" +) + +func main() { + // SHA-256 + c := crypt.SHA256.New() + hash, _ := c.Generate([]byte("secret"), []byte("$5$salt")) + fmt.Println(hash) + + // Traditional DES (legacy verification) + des := crypt.DES.New() + desHash, _ := des.Generate([]byte("foob"), []byte("ar")) + fmt.Println(desHash) // arlEKn0OzVJn. +} +``` + +## Security Note + +Traditional DES-based crypt(3) is cryptographically obsolete and provided exclusively +for backwards compatibility, legacy password verification, and data migration. + ## Documentation The documentation is available on diff --git a/crypt.go b/crypt.go index be0b1ab..90f2bc9 100644 --- a/crypt.go +++ b/crypt.go @@ -50,12 +50,14 @@ const ( MD5 // import github.com/tredoe/crypt/md5_crypt SHA256 // import github.com/tredoe/crypt/sha256_crypt SHA512 // import github.com/tredoe/crypt/sha512_crypt + DES // import github.com/tredoe/crypt/des_crypt maxCrypt ) var ( - crypts = make([]func() Crypter, maxCrypt) - cryptPrefixes = make([]string, maxCrypt) + crypts = make([]func() Crypter, maxCrypt) + cryptPrefixes = make([]string, maxCrypt) + fallbackValidators = make([]func(string) bool, maxCrypt) ) // * * * @@ -63,12 +65,20 @@ var ( // New returns a new crypter. func New(c Crypt) Crypter { return c.New() } -// NewFromHash returns a new Crypter using the prefix in the given hashed key. +// NewFromHash returns a new Crypter using the prefix in the given hashed key, or matching fallback validator. func NewFromHash(hashedKey string) (Crypter, error) { for i := range cryptPrefixes { prefix := cryptPrefixes[i] - if crypts[i] != nil && strings.HasPrefix(hashedKey, prefix) { + if prefix != "" && crypts[i] != nil && strings.HasPrefix(hashedKey, prefix) { + c := Crypt(uint(i)) + return c.New(), nil + } + } + + for i := range fallbackValidators { + v := fallbackValidators[i] + if v != nil && crypts[i] != nil && v(hashedKey) { c := Crypt(uint(i)) return c.New(), nil } @@ -81,7 +91,7 @@ func NewFromHash(hashedKey string) (Crypter, error) { nDollar := strings.Count(hashedKey, "$") - if hashedKey[0] != '$' || nDollar < 3 || nDollar > 4 { + if len(hashedKey) == 0 || hashedKey[0] != '$' || nDollar < 3 || nDollar > 4 { return nil, ErrUnknown } return nil, UnknownError(hashedKey) @@ -114,3 +124,13 @@ func RegisterCrypt(c Crypt, f func() Crypter, prefix string) { crypts[c] = f cryptPrefixes[c] = prefix } + +// RegisterFallback registers a fallback validator function for prefixless +// crypt functions (such as traditional DES crypt). +func RegisterFallback(c Crypt, f func() Crypter, validator func(string) bool) { + if c >= maxCrypt { + panic(ErrUnknown) + } + crypts[c] = f + fallbackValidators[c] = validator +} diff --git a/crypt_test.go b/crypt_test.go index a9bad43..5e1e783 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -9,6 +9,7 @@ import ( "github.com/tredoe/crypt" _ "github.com/tredoe/crypt/apr1_crypt" + _ "github.com/tredoe/crypt/des_crypt" ) func TestSupport(t *testing.T) { @@ -25,4 +26,32 @@ func TestSupport(t *testing.T) { if !strings.HasSuffix(err.Error(), "$unknown$") { t.Error("expect that error got the crypt magic identifier") } + + // Traditional DES hash + c, err := crypt.NewFromHash("arlEKn0OzVJn.") + if err != nil { + t.Errorf("expect support for DES hash: %v", err) + } + if err := c.Verify("arlEKn0OzVJn.", []byte("foob")); err != nil { + t.Errorf("verify failed for DES hash: %v", err) + } + + // Invalid DES hash (wrong trailing bits) + if _, err := crypt.NewFromHash("arlEKn0OzVJn/"); err == nil { + t.Errorf("expected error for invalid DES hash") + } +} + +func TestNewDES(t *testing.T) { + if !crypt.DES.Available() { + t.Fatalf("crypt.DES should be available") + } + c := crypt.DES.New() + hash, err := c.Generate([]byte("foob"), []byte("ar")) + if err != nil { + t.Fatalf("Generate returned error: %v", err) + } + if hash != "arlEKn0OzVJn." { + t.Errorf("expected arlEKn0OzVJn., got %q", hash) + } } diff --git a/des_crypt/des_crypt.go b/des_crypt/des_crypt.go new file mode 100644 index 0000000..dd1d99b --- /dev/null +++ b/des_crypt/des_crypt.go @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2019-2026 Francois Pesce +// SPDX-License-Identifier: BSD-2-Clause + +// Package des_crypt implements the traditional Unix DES crypt(3) password hashing algorithm. +package des_crypt + +import ( + "crypto/rand" + "crypto/subtle" + "errors" + + "github.com/tredoe/crypt" + "github.com/tredoe/crypt/common" +) + +func init() { + crypt.RegisterCrypt(crypt.DES, New, "") + crypt.RegisterFallback(crypt.DES, New, Validate) +} + +const ( + SaltLenMin = 2 + SaltLenMax = 2 + RoundsDefault = 25 + RoundsMin = 25 + RoundsMax = 25 + HashLen = 13 +) + +var ( + ErrSaltLength = errors.New("des_crypt: salt must be at least 2 bytes") +) + +type crypter struct { + Salt common.Salt +} + +// New returns a new crypt.Crypter computing the traditional DES crypt(3) password hashing. +func New() crypt.Crypter { + return &crypter{ + Salt: common.Salt{ + MagicPrefix: []byte(""), + SaltLenMin: SaltLenMin, + SaltLenMax: SaltLenMax, + RoundsDefault: RoundsDefault, + RoundsMin: RoundsMin, + RoundsMax: RoundsMax, + }, + } +} + +// Validate checks if a string is a valid 13-character DES crypt(3) hash. +// It verifies the exact length, characters in the 64-symbol alphabet, +// and the 2-bit zero-mask on the 13th character. +func Validate(hash string) bool { + if len(hash) != HashLen { + return false + } + for i := 0; i < HashLen; i++ { + b := hash[i] + if !((b >= '.' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')) { + return false + } + } + if ascii_to_bin[hash[12]]&0x03 != 0 { + return false + } + return true +} + +func (c *crypter) Generate(key, salt []byte) (string, error) { + var s [2]byte + if len(salt) == 0 { + var randBytes [2]byte + if _, err := rand.Read(randBytes[:]); err != nil { + return "", err + } + s[0] = ascii64Bytes[randBytes[0]&0x3f] + s[1] = ascii64Bytes[randBytes[1]&0x3f] + } else if len(salt) < 2 { + return "", ErrSaltLength + } else { + for i := 0; i < 2; i++ { + b := salt[i] + if !((b >= '.' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')) { + return "", common.ErrSaltFormat + } + } + s[0] = salt[0] + s[1] = salt[1] + } + + var k [8]byte + keyLen := len(key) + if keyLen > 8 { + keyLen = 8 + } + copy(k[:], key[:keyLen]) + + return DESCrypt(k, s), nil +} + +func (c *crypter) Verify(hashedKey string, key []byte) error { + if !Validate(hashedKey) { + return crypt.ErrKeyMismatch + } + newHash, err := c.Generate(key, []byte(hashedKey)) + if err != nil { + return err + } + if subtle.ConstantTimeCompare([]byte(newHash), []byte(hashedKey)) != 1 { + return crypt.ErrKeyMismatch + } + return nil +} + +func (c *crypter) Cost(hashedKey string) (int, error) { + return RoundsDefault, nil +} + +func (c *crypter) SetSalt(salt common.Salt) { + c.Salt = salt +} diff --git a/des_crypt/des_crypt_test.go b/des_crypt/des_crypt_test.go new file mode 100644 index 0000000..4ddc2b9 --- /dev/null +++ b/des_crypt/des_crypt_test.go @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2019-2026 Francois Pesce +// SPDX-License-Identifier: BSD-2-Clause + +package des_crypt + +import ( + "testing" + + "github.com/tredoe/crypt" +) + +func TestDESCryptGenerate(t *testing.T) { + c := New() + + tests := []struct { + key string + salt string + expected string + }{ + {"foob", "ar", "arlEKn0OzVJn."}, + {"test", "PQ", "PQl1.p7BcJRuM"}, + {"much lon", "xx", "xxtHrOGVa3182"}, + {"much longer password", "xx", "xxtHrOGVa3182"}, // Key truncated to first 8 bytes + {"foob", "arlEKn0OzVJn.", "arlEKn0OzVJn."}, // Full hash passed as salt + } + + for _, tt := range tests { + hash, err := c.Generate([]byte(tt.key), []byte(tt.salt)) + if err != nil { + t.Fatalf("Generate(%q, %q) returned error: %v", tt.key, tt.salt, err) + } + if hash != tt.expected { + t.Errorf("Generate(%q, %q) = %q; expected %q", tt.key, tt.salt, hash, tt.expected) + } + } +} + +func TestDESCryptGenerateRandomSalt(t *testing.T) { + c := New() + hash, err := c.Generate([]byte("password"), nil) + if err != nil { + t.Fatalf("Generate with nil salt returned error: %v", err) + } + if len(hash) != 13 { + t.Fatalf("Expected hash length 13, got %d (%q)", len(hash), hash) + } + if !Validate(hash) { + t.Errorf("Generated hash %q failed validation", hash) + } + // Verify that password verifies against generated hash + if err := c.Verify(hash, []byte("password")); err != nil { + t.Errorf("Verify failed for auto-generated hash: %v", err) + } +} + +func TestDESCryptInvalidSalts(t *testing.T) { + c := New() + + // Salt too short + if _, err := c.Generate([]byte("password"), []byte("a")); err == nil { + t.Errorf("Expected error for 1-byte salt, got nil") + } + + // Salt contains invalid character + if _, err := c.Generate([]byte("password"), []byte("a!")); err == nil { + t.Errorf("Expected error for invalid salt char, got nil") + } +} + +func TestDESCryptValidate(t *testing.T) { + validHashes := []string{ + "arlEKn0OzVJn.", + "PQl1.p7BcJRuM", + "xxtHrOGVa3182", + "aaXXXXXXXXXX.", + } + for _, h := range validHashes { + if !Validate(h) { + t.Errorf("Validate(%q) = false; expected true", h) + } + } + + invalidHashes := []string{ + "arlEKn0OzVJn", // 12 chars (too short) + "arlEKn0OzVJn..", // 14 chars (too long) + "arlEKn0OzVJ!", // Invalid char '!' + "$1$foobar$xxxx", // MD5 format + // Last char constraint violation: + // in ascii64Bytes, '.' is 0 (000000b), '/' is 1 (000001b, lower 2 bits != 0). + // Replacing valid '.' at index 12 with '/' violates 2-bit zero mask + "arlEKn0OzVJn/", + } + for _, h := range invalidHashes { + if Validate(h) { + t.Errorf("Validate(%q) = true; expected false", h) + } + } +} + +func TestDESCryptVerify(t *testing.T) { + c := New() + + // Correct password + if err := c.Verify("arlEKn0OzVJn.", []byte("foob")); err != nil { + t.Errorf("Verify failed for valid key: %v", err) + } + + // Password truncation works in Verify (> 8 bytes) + if err := c.Verify("xxtHrOGVa3182", []byte("much longer password")); err != nil { + t.Errorf("Verify failed for long password: %v", err) + } + + // Incorrect password + if err := c.Verify("arlEKn0OzVJn.", []byte("wrong")); err != crypt.ErrKeyMismatch { + t.Errorf("Expected ErrKeyMismatch for wrong key, got: %v", err) + } + + // Invalid hash format + if err := c.Verify("invalidhash", []byte("foob")); err != crypt.ErrKeyMismatch { + t.Errorf("Expected ErrKeyMismatch for invalid hash, got: %v", err) + } +} + +func TestDESCryptCost(t *testing.T) { + c := New() + cost, err := c.Cost("arlEKn0OzVJn.") + if err != nil { + t.Fatalf("Cost returned error: %v", err) + } + if cost != RoundsDefault { + t.Errorf("Cost = %d; expected %d", cost, RoundsDefault) + } +} + +func BenchmarkGenerate(b *testing.B) { + c := New() + key := []byte("password") + salt := []byte("ar") + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = c.Generate(key, salt) + } +} + +func BenchmarkVerify(b *testing.B) { + c := New() + key := []byte("foob") + hash := "arlEKn0OzVJn." + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = c.Verify(hash, key) + } +} diff --git a/des_crypt/engine.go b/des_crypt/engine.go new file mode 100644 index 0000000..afe6bd1 --- /dev/null +++ b/des_crypt/engine.go @@ -0,0 +1,353 @@ +// SPDX-FileCopyrightText: 2019-2026 Francois Pesce +// SPDX-License-Identifier: BSD-2-Clause + +package des_crypt + +import ( + "strings" +) + +var IP = [64]byte{58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7} + +var inv_key_perm [64]byte +var key_perm = [56]byte{57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4} + +var key_shifts = [16]byte{1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1} + +var inv_comp_perm [56]byte +var comp_perm = [48]byte{14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32} + +/* + * No E box is used, as it's replaced by some ANDs, shifts, and ORs. + */ +var u_sbox [8][64]byte +var sbox = [8][64]byte{ + {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}, + {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}, + {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}, + {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}, + {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}, + {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}, + {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}, + {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}} + +var un_pbox [32]byte +var pbox = [32]byte{16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25} + +func bits32x(i, o int) uint32 { + return 1 << (31 - (uint32(i) + uint32(o))) +} +func bits32(i int) uint32 { + return bits32x(i, 0) +} +func bits28(i int) uint32 { + return bits32x(i, 4) +} +func bits24(i int) uint32 { + return bits32x(i, 8) +} +func bits8(i int) byte { + return 1 << (7 - uint32(i)) +} + +var init_perm [64]byte +var final_perm [64]byte +var m_sbox [4][4096]byte +var psbox [4][256]uint32 +var ip_maskl [8][256]uint32 +var ip_maskr [8][256]uint32 +var fp_maskl [8][256]uint32 +var fp_maskr [8][256]uint32 +var key_perm_maskl [8][128]uint32 +var key_perm_maskr [8][128]uint32 +var comp_maskl [8][128]uint32 +var comp_maskr [8][128]uint32 + +const ascii64Bytes = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + +var ascii_to_bin = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 0, 0, 0, 0, 0, 0, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +func init() { + var i, j, b, k, inbit, obit int + var p, il, ir, fl, fr *uint32 + + /* + * Invert the S-boxes, reordering the input bits. + */ + for i = 0; i < 8; i++ { + for j = 0; j < 64; j++ { + b = (j & 0x20) | ((j & 1) << 4) | ((j >> 1) & 0xf) + u_sbox[i][j] = sbox[i][b] + } + } + + /* + * Convert the inverted S-boxes into 4 arrays of 8 bits. + * Each will handle 12 bits of the S-box input. + */ + for b = 0; b < 4; b++ { + for i = 0; i < 64; i++ { + for j = 0; j < 64; j++ { + m_sbox[b][(i<<6)|j] = byte((u_sbox[(b << 1)][i] << 4) | u_sbox[(b<<1)+1][j]) + } + } + } + + /* + * Set up the initial & final permutations into a useful form, and + * initialise the inverted key permutation. + */ + for i = 0; i < 64; i++ { + final_perm[i] = IP[i] - 1 + init_perm[final_perm[i]] = byte(i) + inv_key_perm[i] = 255 + } + + /* + * Invert the key permutation and initialise the inverted key + * compression permutation. + */ + for i = 0; i < 56; i++ { + inv_key_perm[key_perm[i]-1] = byte(i) + inv_comp_perm[i] = 255 + } + + /* + * Invert the key compression permutation. + */ + for i = 0; i < 48; i++ { + inv_comp_perm[comp_perm[i]-1] = byte(i) + } + + /* + * Set up the OR-mask arrays for the initial and final permutations, + * and for the key initial and compression permutations. + */ + for k = 0; k < 8; k++ { + for i = 0; i < 256; i++ { + il = &ip_maskl[k][i] + ir = &ip_maskr[k][i] + fl = &fp_maskl[k][i] + fr = &fp_maskr[k][i] + for j = 0; j < 8; j++ { + inbit = 8*k + j + if byte(0) != (byte(i) & bits8(j)) { + obit = int(init_perm[inbit]) + if obit < 32 { + *il |= bits32(obit) + } else { + *ir |= bits32(obit - 32) + } + obit = int(final_perm[inbit]) + if obit < 32 { + *fl |= bits32(obit) + } else { + *fr |= bits32(obit - 32) + } + } + } + } + for i = 0; i < 128; i++ { + il = &key_perm_maskl[k][i] + ir = &key_perm_maskr[k][i] + for j = 0; j < 7; j++ { + inbit = 8*k + j + if byte(0) != (byte(i) & bits8(j+1)) { + obit = int(inv_key_perm[inbit]) + if obit == 255 { + continue + } + if obit < 28 { + *il |= bits28(obit) + } else { + *ir |= bits28(obit - 28) + } + } + } + il = &comp_maskl[k][i] + ir = &comp_maskr[k][i] + for j = 0; j < 7; j++ { + inbit = 7*k + j + if byte(0) != (byte(i) & bits8(j+1)) { + obit = int(inv_comp_perm[inbit]) + if obit == 255 { + continue + } + if obit < 24 { + *il |= bits24(obit) + } else { + *ir |= bits24(obit - 24) + } + } + } + } + } + + /* + * Invert the P-box permutation, and convert into OR-masks for + * handling the output of the S-box arrays setup above. + */ + for i = 0; i < 32; i++ { + un_pbox[pbox[i]-1] = byte(i) + } + + for b = 0; b < 4; b++ { + for i = 0; i < 256; i++ { + p = &psbox[b][i] + for j = 0; j < 8; j++ { + if byte(0) != (byte(i) & bits8(j)) { + *p |= bits32(int(un_pbox[8*b+j])) + } + } + } + } +} + +func leBswap32(x [8]byte) (uint32, uint32) { + return uint32(x[0])<<24 | uint32(x[1])<<16 | uint32(x[2])<<8 | uint32(x[3]), uint32(x[4])<<24 | uint32(x[5])<<16 | uint32(x[6])<<8 | uint32(x[7]) +} + +const count = 25 +const round = 16 + +func desSetKey(key [8]byte) (en_keysl, en_keysr [16]uint32) { + var k0, k1 uint32 + var shifts int + + rawkey0, rawkey1 := leBswap32(key) + /* + * Do key permutation and split into two 28-bit subkeys. + */ + k0 = key_perm_maskl[0][rawkey0>>25] | key_perm_maskl[1][(rawkey0>>17)&0x7f] | key_perm_maskl[2][(rawkey0>>9)&0x7f] | key_perm_maskl[3][(rawkey0>>1)&0x7f] | key_perm_maskl[4][rawkey1>>25] | key_perm_maskl[5][(rawkey1>>17)&0x7f] | key_perm_maskl[6][(rawkey1>>9)&0x7f] | key_perm_maskl[7][(rawkey1>>1)&0x7f] + k1 = key_perm_maskr[0][rawkey0>>25] | key_perm_maskr[1][(rawkey0>>17)&0x7f] | key_perm_maskr[2][(rawkey0>>9)&0x7f] | key_perm_maskr[3][(rawkey0>>1)&0x7f] | key_perm_maskr[4][rawkey1>>25] | key_perm_maskr[5][(rawkey1>>17)&0x7f] | key_perm_maskr[6][(rawkey1>>9)&0x7f] | key_perm_maskr[7][(rawkey1>>1)&0x7f] + /* + * Rotate subkeys and do compression permutation. + */ + shifts = 0 + for r := 0; r < round; r++ { + var t0, t1 uint32 + + shifts += int(key_shifts[r]) + + t0 = (k0 << uint32(shifts)) | (k0 >> (28 - uint32(shifts))) + t1 = (k1 << uint32(shifts)) | (k1 >> (28 - uint32(shifts))) + + en_keysl[r] = comp_maskl[0][(t0>>21)&0x7f] | comp_maskl[1][(t0>>14)&0x7f] | comp_maskl[2][(t0>>7)&0x7f] | comp_maskl[3][t0&0x7f] | comp_maskl[4][(t1>>21)&0x7f] | comp_maskl[5][(t1>>14)&0x7f] | comp_maskl[6][(t1>>7)&0x7f] | comp_maskl[7][t1&0x7f] + en_keysr[r] = comp_maskr[0][(t0>>21)&0x7f] | comp_maskr[1][(t0>>14)&0x7f] | comp_maskr[2][(t0>>7)&0x7f] | comp_maskr[3][t0&0x7f] | comp_maskr[4][(t1>>21)&0x7f] | comp_maskr[5][(t1>>14)&0x7f] | comp_maskr[6][(t1>>7)&0x7f] | comp_maskr[7][t1&0x7f] + } + + return en_keysl, en_keysr +} + +func doDES(en_keysl, en_keysr [16]uint32, saltbits uint32, l_out, r_out *uint32) { + /* + * l_out, and r_out are in pseudo-"big-endian" format. + */ + var l, r uint32 + var f, r48l, r48r uint32 + + for i := 0; i < count; i++ { + /* + * Do each round. + */ + for j := uint32(0); j < round; j++ { + /* + * Expand R to 48 bits (simulate the E-box). + */ + r48l = ((r & 0x00000001) << 23) | ((r & 0xf8000000) >> 9) | ((r & 0x1f800000) >> 11) | ((r & 0x01f80000) >> 13) | ((r & 0x001f8000) >> 15) + r48r = ((r & 0x0001f800) << 7) | ((r & 0x00001f80) << 5) | ((r & 0x000001f8) << 3) | ((r & 0x0000001f) << 1) | ((r & 0x80000000) >> 31) + /* + * Do salting for crypt() and friends, and + * XOR with the permuted key. + */ + f = (r48l ^ r48r) & saltbits + r48l ^= f ^ en_keysl[j] + r48r ^= f ^ en_keysr[j] + /* + * Do sbox lookups (which shrink it back to 32 bits) + * and do the pbox permutation at the same time. + */ + f = psbox[0][m_sbox[0][r48l>>12]] | psbox[1][m_sbox[1][r48l&0xfff]] | psbox[2][m_sbox[2][r48r>>12]] | psbox[3][m_sbox[3][r48r&0xfff]] + /* + * Now that we've permuted things, complete f(). + */ + f ^= l + l = r + r = f + } + r = l + l = f + } + /* + * Do final permutation (inverse of IP). + */ + *l_out = fp_maskl[0][l>>24] | fp_maskl[1][(l>>16)&0xff] | fp_maskl[2][(l>>8)&0xff] | fp_maskl[3][l&0xff] | fp_maskl[4][r>>24] | fp_maskl[5][(r>>16)&0xff] | fp_maskl[6][(r>>8)&0xff] | fp_maskl[7][r&0xff] + *r_out = fp_maskr[0][l>>24] | fp_maskr[1][(l>>16)&0xff] | fp_maskr[2][(l>>8)&0xff] | fp_maskr[3][l&0xff] | fp_maskr[4][r>>24] | fp_maskr[5][(r>>16)&0xff] | fp_maskr[6][(r>>8)&0xff] | fp_maskr[7][r&0xff] +} + +func setupSalt(salt uint32) uint32 { + var obit, saltbit, saltbits uint32 + + saltbit = 1 + obit = 0x800000 + for i := 0; i < 24; i++ { + if (salt & saltbit) != 0 { + saltbits |= obit + } + saltbit <<= 1 + obit >>= 1 + } + + return saltbits +} + +func DESCryptGetSaltUI(setting [2]byte) uint32 { + return uint32(ascii_to_bin[setting[1]])<<6 | uint32(ascii_to_bin[setting[0]]) +} + +func DESCryptGetSaltBits(setting [2]byte) uint32 { + return setupSalt(uint32(ascii_to_bin[setting[1]])<<6 | uint32(ascii_to_bin[setting[0]])) +} + +func DESCryptRaw(key [8]byte, saltbits uint32) (r0, r1 uint32) { + /* shifting each character up by one bit */ + for i := 0; i < 8; i++ { + if 0 != key[i] { + key[i] <<= 1 + } + } + en_keysl, en_keysr := desSetKey(key) + doDES(en_keysl, en_keysr, saltbits, &r0, &r1) + + return r0, r1 +} + +func DESCrypt(key [8]byte, setting [2]byte) string { + var out strings.Builder + + saltbits := DESCryptGetSaltBits(setting) + r0, r1 := DESCryptRaw(key, saltbits) + out.WriteByte(setting[0]) + out.WriteByte(setting[1]) + /* + * Now encode the result... + */ + l := (r0 >> 8) + out.WriteByte(ascii64Bytes[(l>>18)&0x3f]) + out.WriteByte(ascii64Bytes[(l>>12)&0x3f]) + out.WriteByte(ascii64Bytes[(l>>6)&0x3f]) + out.WriteByte(ascii64Bytes[l&0x3f]) + + l = (r0 << 16) | ((r1 >> 16) & 0xffff) + out.WriteByte(ascii64Bytes[(l>>18)&0x3f]) + out.WriteByte(ascii64Bytes[(l>>12)&0x3f]) + out.WriteByte(ascii64Bytes[(l>>6)&0x3f]) + out.WriteByte(ascii64Bytes[l&0x3f]) + + l = r1 << 2 + out.WriteByte(ascii64Bytes[(l>>12)&0x3f]) + out.WriteByte(ascii64Bytes[(l>>6)&0x3f]) + out.WriteByte(ascii64Bytes[l&0x3f]) + + return out.String() +} diff --git a/example_test.go b/example_test.go index 16a0a04..b7894b6 100644 --- a/example_test.go +++ b/example_test.go @@ -7,18 +7,32 @@ import ( "fmt" "github.com/tredoe/crypt" + _ "github.com/tredoe/crypt/des_crypt" _ "github.com/tredoe/crypt/sha256_crypt" ) func ExampleCrypt() { - crypt := crypt.SHA256.New() - ret, _ := crypt.Generate([]byte("secret"), []byte("$5$salt")) + cryptInstance := crypt.SHA256.New() + ret, _ := cryptInstance.Generate([]byte("secret"), []byte("$5$salt")) fmt.Println(ret) - err := crypt.Verify(ret, []byte("secret")) + err := cryptInstance.Verify(ret, []byte("secret")) fmt.Println(err) // Output: // $5$salt$kpa26zwgX83BPSR8d7w93OIXbFt/d3UOTZaAu5vsTM6 // } + +func ExampleCrypt_des() { + desCrypt := crypt.DES.New() + ret, _ := desCrypt.Generate([]byte("foob"), []byte("ar")) + fmt.Println(ret) + + err := desCrypt.Verify(ret, []byte("foob")) + fmt.Println(err) + + // Output: + // arlEKn0OzVJn. + // +}