// Package crypto cifra as senhas da PontoMais (AES-256-GCM), faz hash das // senhas do painel (Argon2id) e gera tokens de sessão. package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "errors" "fmt" "io" ) const MinKeyLength = 32 var ErrUnknownKeyVersion = errors.New("versão de chave de criptografia desconhecida") // Keyring guarda a chave atual e, opcionalmente, a anterior. Cada senha cifrada // registra a versão usada (point_credentials.key_version), o que permite trocar // a chave sem perder as credenciais já gravadas (comando admin rotate-key). type Keyring struct { current int16 keys map[int16][]byte } // NewKeyring recebe o segredo atual e sua versão; previous é opcional (vazio = sem chave anterior). func NewKeyring(current string, currentVersion int16, previous string, previousVersion int16) (*Keyring, error) { if len(current) < MinKeyLength { return nil, fmt.Errorf("a chave de criptografia precisa ter pelo menos %d caracteres", MinKeyLength) } k := &Keyring{current: currentVersion, keys: map[int16][]byte{currentVersion: derive(current)}} if previous != "" { if previousVersion == currentVersion { return nil, errors.New("a chave anterior precisa ter uma versão diferente da atual") } if len(previous) < MinKeyLength { return nil, fmt.Errorf("a chave anterior precisa ter pelo menos %d caracteres", MinKeyLength) } k.keys[previousVersion] = derive(previous) } return k, nil } func derive(secret string) []byte { sum := sha256.Sum256([]byte(secret)) return sum[:] } func (k *Keyring) CurrentVersion() int16 { return k.current } // Encrypt cifra com a chave atual e devolve nonce||ciphertext e a versão usada. func (k *Keyring) Encrypt(plain string) ([]byte, int16, error) { gcm, err := newGCM(k.keys[k.current]) if err != nil { return nil, 0, err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, 0, err } return gcm.Seal(nonce, nonce, []byte(plain), nil), k.current, nil } func (k *Keyring) Decrypt(data []byte, version int16) (string, error) { key, ok := k.keys[version] if !ok { return "", fmt.Errorf("%w: %d", ErrUnknownKeyVersion, version) } gcm, err := newGCM(key) if err != nil { return "", err } if len(data) < gcm.NonceSize() { return "", errors.New("credencial cifrada inválida") } plain, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) if err != nil { return "", errors.New("não foi possível decifrar a credencial (chave incorreta?)") } return string(plain), nil } func newGCM(key []byte) (cipher.AEAD, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewGCM(block) }