package cookie import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "strings" ) type DecryptEncrypt struct { _new_cookie_key_ string } func New() *DecryptEncrypt { return &DecryptEncrypt{ _new_cookie_key_: "def000008bf3d70e7012b7493c382d561e193218d0c74ab162fb0ea8029ce20e926531b4bcf0aaec9381152e6c161f198e06918b2d1aad67cc7cf40819a51ee328c63830", } } const ( CurrentVersion = "\xDE\xF5\x02\x00" KeyCurrentVersion = "\xDE\xF0\x00\x00" SaltSize = 32 IVSize = 16 MacSize = 32 MinCiphertextSize = 84 KeyByteSize = 32 ChecksumSize = 32 HeaderSize = 4 AuthInfo = "DefusePHP|V2|KeyForAuthentication" EncInfo = "DefusePHP|V2|KeyForEncryption" PBKDF2Iterations = 100000 ) // === DECRYPT === func (d *DecryptEncrypt) DecryptInternal(ciphertextHex string, kp *KeyOrPassword, rawBinary bool) ([]byte, error) { var ct []byte var err error if !rawBinary { ct, err = d.hexToBin(ciphertextHex) if err != nil { return nil, errors.New("invalid hex") } } else { ct = []byte(ciphertextHex) } if len(ct) < MinCiphertextSize { return nil, errors.New("ciphertext too short") } // Extract parts header := ct[:HeaderSize] if string(header) != CurrentVersion { return nil, errors.New("bad version") } salt := ct[HeaderSize : HeaderSize+SaltSize] iv := ct[HeaderSize+SaltSize : HeaderSize+SaltSize+IVSize] hmacStart := len(ct) - MacSize // expectedHMAC := ct[hmacStart:] encStart := HeaderSize + SaltSize + IVSize encrypted := ct[encStart:hmacStart] // Derive keys keys, err := kp.DeriveKeys(salt) if err != nil { return nil, err } // === HMAC: salt || iv || encrypted === // message := append(append(salt, iv...), encrypted...) // if !verifyHMAC(expectedHMAC, message, keys.akey) { // return nil, errors.New("integrity check failed") // } // Decrypt return d.aesCTRDecrypt(encrypted, keys.ekey, iv) } func (d *DecryptEncrypt) aesCTRDecrypt(ciphertext, key, iv []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } plaintext := make([]byte, len(ciphertext)) stream := cipher.NewCTR(block, iv) stream.XORKeyStream(plaintext, ciphertext) return plaintext, nil } // === ENCRYPT === func (d *DecryptEncrypt) EncryptInternal(plaintext, domain string) (string, error) { key, err := d.LoadKeyFromASCII() if err != nil { return "", err } kp := &KeyOrPassword{SecretType: 1, Key: key} salt := make([]byte, SaltSize) if _, err := rand.Read(salt); err != nil { return "", err } iv := make([]byte, IVSize) if _, err := rand.Read(iv); err != nil { return "", err } keys, err := kp.DeriveKeys(salt) if err != nil { return "", err } encrypted, err := d.aesCTREncrypt([]byte(plaintext), keys.ekey, iv) if err != nil { return "", err } // HMAC over: salt || iv || ciphertext message := append(append(salt, iv...), encrypted...) hmacVal := hmac.New(sha256.New, keys.akey) hmacVal.Write(message) mac := hmacVal.Sum(nil) // Assemble: header || salt || iv || encrypted || mac result := append([]byte(CurrentVersion), salt...) result = append(result, iv...) result = append(result, encrypted...) result = append(result, mac...) return hex.EncodeToString(result), nil } func (d DecryptEncrypt) aesCTREncrypt(plaintext, key, iv []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } ciphertext := make([]byte, len(plaintext)) stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext, plaintext) return ciphertext, nil } func (d *DecryptEncrypt) LoadKeyFromASCII() (*Key, error) { b, err := d.loadBytesFromChecksummedASCII(KeyCurrentVersion) if err != nil { return nil, err } if len(b) != KeyByteSize { return nil, errors.New("bad key length") } return &Key{bytes: b}, nil } func (d *DecryptEncrypt) loadBytesFromChecksummedASCII(expectedHeader string) ([]byte, error) { data, err := d.hexToBin(d._new_cookie_key_) if err != nil { return nil, err } if len(data) < HeaderSize+ChecksumSize { return nil, errors.New("data too short") } if string(data[:HeaderSize]) != expectedHeader { return nil, errors.New("invalid header") } payloadLen := len(data) - ChecksumSize checked := data[:payloadLen] sum := sha256.Sum256(checked) if !hmac.Equal(sum[:], data[payloadLen:]) { return nil, errors.New("checksum mismatch") } return data[HeaderSize:payloadLen], nil } // func verifyHMAC(expected, msg, key []byte) bool { // mac := hmac.New(sha256.New, key) // mac.Write(msg) // return hmac.Equal(mac.Sum(nil), expected) // } // === hexToBin === func (d *DecryptEncrypt) hexToBin(s string) ([]byte, error) { s = strings.ToLower(s) if len(s)%2 != 0 { return nil, errors.New("odd length hex") } b := make([]byte, len(s)/2) for i := 0; i < len(s); i += 2 { hi := d.hexChar(s[i]) lo := d.hexChar(s[i+1]) if hi < 0 || lo < 0 { return nil, errors.New("invalid hex char") } b[i/2] = byte(hi<<4 | lo) } return b, nil } func (d *DecryptEncrypt) hexChar(c byte) int { switch { case '0' <= c && c <= '9': return int(c - '0') case 'a' <= c && c <= 'f': return int(c-'a') + 10 default: return -1 } }