Je suis en train d'écrire un logiciel qui va interagir avec Bitwarden. Il existe plusieurs clients Bitwarden, mais peu sont suffisamment complet pour mon besoin.
Ce billet fait suite au premier billet écrire un client Bitwarden en python : créer, connecter, valider un compte.
Voyons maintenant comment créer un "identifiant" dans son propre coffre.
Déchiffrer des données avec clé symétrique
Avant tout, nous allons voir comment déchiffrer des données avec sa clé symétrique.
Comme nous l'avons vu, on reconnait du contenu chiffré par une clé symétrique lorsque la chaine ressemble à :
f'2.{b64_iv}|{b64_ct}|{b64_digest}'
Pour déchiffrer nous avons besoin de la clé symétrique découpé comme cela :
enc = sym_key[:32] mac = sym_key[32:]
Voici comment déchiffrer le contenu de la variable cipher :
type, cipher_data = cipher.split('.', 1) assert type == 2 # it's a symetric key iv, ct, cipher_mac = (b64decode(sdata) for sdata in cipher_data.split('|', 2)) cmac = hmac_new(mac, iv, ct, sha256) assert cipher_mac == cmac.digest() # it's the good key plaintext = AES.new(enc, AES.MODE_CBC, iv).decrypt(ct) pad_len = plaintext[-1] padding = bytes([pad_len] * pad_len) if plaintext[-pad_len:] == padding: plaintext = plaintext[:-pad_len]
Chiffrer les données
Pour chiffrer les données, il faut commencer par récupérer la clé de chiffrement symétrique (la variable login ci-dessous provient de la connexion de l'utilisateur) :
master_key = login['master_key'] enc = hkdf_expand(master_key, b'enc', 32, sha256) mac = hkdf_expand(master_key, b'mac', 32, sha256) cipher = login['Key']
On peut récupérer la valeur de la clé symétrique en déchiffrant son contenu comme vu dans le paragraphe précédent :
sym_key = plaintext assert len(sym_key) == 64 # the symetric key has 64 characters
La clé de chiffrement est stockée dans la variable sym_key.
Pour chiffrer le contenu (la variable "content" ci-dessous) avec la clé symétrique il faut faire (comme nous l'avons déjà vu) :
content = content.encode() enc = sym_key[:32] mac = sym_key[32:] iv = token_bytes(16) pad_len = 16 - len(content) % 16 padding = bytes([ pad_len ] * pad_len) ct = AES.new(enc, AES.MODE_CBC, iv).encrypt(content + padding) cmac = hmac_new(mac, iv + ct, sha256)
Puis stocker dans une chaine les informations :
type = 2 b64_iv = b64encode(iv).decode() b64_ct = b64encode(ct).decode() b64_digest = b64encode(cmac.digest()).decode() encoded_content = f'{type}.{b64_iv}|{b64_ct}|{b64_digest}'
Créer un identifiant
Pour créer un identifiant nous avons besoin :
- d'un nom d'identifiant
- d'un nom d'utilisateur
- d'un mot de passe
Ces trois informations doivent être chiffrées avec la clé symétrique comme vu ci-dessus :
data = {"type": 1, "folderId": None, "organizationId": None, "name": encoded_name, "notes": None, "favorite": False, "login":{ "response": None, "uris": None, "username": encoded_username, "password": encoded_password, "passwordRevisionDate": None, "totp": None } }
Il faut faire un POST avec ce payload sur l'URL https://localhost:8001/api/ciphers pour créer l'identifiant. Dans l'entête il faut rajouter les informations d'authentification :
{'Authorization': f'Bearer {login["access_token"]}'}
La suite
Nous verrons par la suite comment créer une "organisation" et une "collection".