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 :

  1. d'un nom d'identifiant
  2. d'un nom d'utilisateur
  3. 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".