package crypto import ( "crypto/cipher" "encoding/hex" "errors" "fmt" "golang.org/x/crypto/blowfish" ) // LegacyDecryptBlowfish decrypts the hex-encoded payload produced by the // original WinAuth Authenticator.Decrypt(string, byte[]) method, which uses // CBC-less Blowfish with ISO10126-2 padding via BouncyCastle's // PaddedBufferedBlockCipher. // // The BouncyCastle "PaddedBufferedBlockCipher" with no IV is effectively // ECB; the original WinAuth code chose Blowfish in that mode and relied on // ISO10126-2 to round the payload to the block size. func LegacyDecryptBlowfish(hexCiphertext string, key []byte) ([]byte, error) { ct, err := hex.DecodeString(hexCiphertext) if err != nil { return nil, fmt.Errorf("legacy blowfish: hex decode: %w", err) } cph, err := blowfish.NewCipher(key) if err != nil { return nil, fmt.Errorf("legacy blowfish: %w", err) } bs := cph.BlockSize() if len(ct)%bs != 0 { return nil, errors.New("legacy blowfish: ciphertext not a multiple of block size") } out := make([]byte, len(ct)) for i := 0; i < len(ct); i += bs { cph.Decrypt(out[i:i+bs], ct[i:i+bs]) } return stripISO10126(out, bs) } // LegacyEncryptBlowfish is provided for symmetry / round-trip tests; the // new format never writes Blowfish. func LegacyEncryptBlowfish(plaintext, key []byte) (string, error) { cph, err := blowfish.NewCipher(key) if err != nil { return "", err } padded, err := padISO10126(plaintext, cph.BlockSize()) if err != nil { return "", err } out := make([]byte, len(padded)) for i := 0; i < len(padded); i += cph.BlockSize() { cph.Encrypt(out[i:i+cph.BlockSize()], padded[i:i+cph.BlockSize()]) } return hex.EncodeToString(out), nil } // stripISO10126 removes ISO 10126-2 padding: last byte = pad length; // preceding bytes are arbitrary. func stripISO10126(buf []byte, blockSize int) ([]byte, error) { if len(buf) == 0 { return nil, errors.New("iso10126: empty buffer") } padLen := int(buf[len(buf)-1]) if padLen <= 0 || padLen > blockSize { return nil, errors.New("iso10126: invalid padding length") } return buf[:len(buf)-padLen], nil } func padISO10126(buf []byte, blockSize int) ([]byte, error) { padLen := blockSize - len(buf)%blockSize out := make([]byte, len(buf)+padLen) copy(out, buf) // fill with deterministic-but-non-zero bytes so tests are reproducible // (real WinAuth uses a CSPRNG; the actual content is ignored on decrypt). for i := len(buf); i < len(out)-1; i++ { out[i] = byte(i) } out[len(out)-1] = byte(padLen) // silence linter for unused cipher variable if any _ = cipher.NewCBCEncrypter return out, nil }