package Config::ClawsMail::PasswordStore; use v5.26; use Moo; # VERSION use experimental 'signatures'; use Types::Standard qw(HashRef); use Crypt::Misc qw(decode_b64); use Crypt::KeyDerivation qw(pbkdf2); use Crypt::Cipher::AES; use Crypt::Mode::CBC; use namespace::clean; # ABSTRACT: decrypt Claws-Mail password store sub PASSCRYPT_KEY() { 'passkey0' } has raw_data => ( is => 'ro', required => 1, isa => HashRef, ); has master_password => ( is => 'ro' ); has master_salt_bs64 => ( is => 'ro', required => 1 ); has master_salt => ( is => 'lazy' ); sub _build_master_salt($self) { decode_b64($self->master_salt_bs64) } sub decrypt($self,$input) { return $input unless $input && $input =~ m{\A \{ ([a-z0-9-]+),(\d+) \} (.+) \z}smxi; my ($algo,$rounds,$ciphertext) = ($1,$2,$3); die 'unknown algo' unless $algo eq 'AES-256-CBC'; my $key = pbkdf2( $self->master_password || PASSCRYPT_KEY(), $self->master_salt, $rounds, 'SHA1', 32, # 32 bytes = 256 bits,for AES-256 ); my $cipher = Crypt::Mode::CBC->new('AES', 5); # 5 = zero padding $ciphertext = decode_b64($ciphertext); # claws sets up 16 random bytes as IV?? my $iv = '0123456789abcdef'; my $cleartext = $cipher->decrypt($ciphertext, $key, $iv); # the first 16 bytes are generated from the IV, we don't care # about them $cleartext = substr($cleartext,16); $cleartext =~ s/\0+\z//; return $cleartext; } sub password_for($self,$section,$key) { return $self->decrypt($self->raw_data->{$section}{$key}); } 1;