Hi everyone,
I’m trying to re-use some functionality of https://github.com/jcs/rubywarden/blob/master/API.md
Which is a Ruby extension api for bitwarden.
I have succeeded to create an entry in bitwarden with /api/ciphers/create route.
I can create the entry with organisation, folder but no data are posted.
To explain a little, in post data, you should encrypt data with a specific format explains here:
https://github.com/cozy/cozy-stack/blob/master/docs/bitwarden.md.
I used the bitwarden browser plugin with burp to check out routes and data posted. I have found that my data are not well encrypted like bitwarden wanted but I don’t know what is wrong.
Here the ruby code to generate Key and encrypt data:
#PBKDF2 is used with a password of $masterPassword, salt of lowercased $email, and $iterations KDF iterations to stretch password into $masterKey.
def makeKey(password, username, iterations)
PBKDF2.new(:password => password, :salt => username,
:iterations => iterations, :hash_function => OpenSSL::Digest::SHA256,
:key_length => (256 / 8)).bin_string
end
encrypt random bytes with a key to make new encryption key
def makeEncKey(key)
# pt[0, 32] becomes the cipher encryption key
# pt[32, 32] becomes the mac key
plaintext = OpenSSL::Random.random_bytes(64)
initvector = OpenSSL::Random.random_bytes(16)
cipher = OpenSSL::Cipher.new "aes-256-cbc-hmac-sha256"
cipher.encrypt
cipher.key = key
cipher.iv = initvector
ciphertext = cipher.update(plaintext)
ciphertext << cipher.final
return cipherString(0, Base64.strict_encode64(initvector), Base64.strict_encode64(ciphertext), nil)
end
#A random, 64-byte key $symmetricKey is created to become the symmetric key. The first 32 bytes become $encKey and the last 32 bytes become $macKey. A random, 16-byte IV $iv is created and $masterKey is used as the key to encrypt $symmetricKey.
#A “CipherString” (a Bitwarden internal format) is created by joining the encryption type (0 for AesCbc256_B64), a dot, the Base64-encoded IV, and the Base64-encoded $encKey and $macKey, with the pipe (|) character to become $protectedKey.
def getmac(cipherString, masterkey)
if cipherString[0].to_i != 0
raise "implement #{cipherString[0].to_i} decryption"
end
# AesCbc256_HmacSha256_B64
initvector, ciphertext = cipherString[2 .. -1].split("|", 2)
initvector = Base64.decode64(initvector)
ciphertext = Base64.decode64(ciphertext)
cipher = OpenSSL::Cipher.new "aes-256-cbc-hmac-sha256"
cipher.decrypt
cipher.iv = initvector
cipher.key = masterkey
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
return plaintext[32, 32]
end
def getenckey(cipherString, masterkey)
if cipherString[0].to_i != 0
raise "implement #{str[0].to_i} decryption"
end
# AesCbc256_HmacSha256_B64
initvector, ciphertext = cipherString[2 .. -1].split("|", 2)
initvector = Base64.decode64(initvector)
ciphertext = Base64.decode64(ciphertext)
cipher = OpenSSL::Cipher.new "aes-256-cbc-hmac-sha256"
cipher.decrypt
cipher.iv = initvector
cipher.key = masterkey
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
return plaintext[0, 32]
end
encrypt+mac a value with a key and mac key and random iv, return cipherString
def encrypt(pt, key, macKey)
iv = OpenSSL::Random.random_bytes(16)
cipher = OpenSSL::Cipher.new "aes-256-cbc-hmac-sha256"
cipher.encrypt
cipher.key = key
cipher.iv = iv
ct = cipher.update(pt)
ct << cipher.final
mac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("SHA256"), macKey, iv + ct)
return cipherString(2, Base64.strict_encode64(iv), Base64.strict_encode64(ct), Base64.strict_encode64(mac))
end
def cipherString(enctype, iv, ct, mac)
[ enctype.to_s + "." + iv, ct, mac ].reject{|p| !p }.join("|")
end
Example of POST DATA
Request
{
"cipher": {
"type": 1,
"folderId": "5857eed8-46ed-4295-a62d-73fde176bae1",
"organizationId": "2f5c89a5-4b3e-417d-b29a-289d675fc384",
"name": "2.vIFnH9e2cejXyXs1XMdiyg==|Ee+mdNJsy8ua9zjatSVxug==|oPLHptK5ZTWcTvLPbOxMqgK8c6+N/ASdP/JmzFex3OE=",
"notes": null,
"favorite": false,
"login": {
"response": null,
"uris": [
{
"response": null,
"match": null,
"uri": "2.E0orUPHrmXnU+jAnNXScDQ==|2INzrxnKpjHW7nD/XSKLVQ==|Dp5liHqFClKnZZ65Yr6Os9lfNJwvkRNKrvSAiTi04Lk="
}
],
"username": "2.z9wIxAMHtJrU0ABvT91ZcA==|vPbfM9cjSXZttdFHcOvRXg==|YJNXaxX5rh/yOn+OycyU/+2agUM0plDtkjzksaj+yXM=",
"password": "2.JG5vjGkMIaBGTtuhQBNUhA==|m7I75JrwQszyHh8R7Wd7hw==|MLnJxXZpw9Fsky8MDvzvalPFv7mAVwChRx+PbHCRn4Q=",
"passwordRevisionDate": null,
"totp": null
}
},
"collectionIds": [
"6f43850b-b547-452b-a646-9d47b163a4ab"
]
}
Response
{
"Attachments": [],
"CollectionIds": [
"6f43850b-b547-452b-a646-9d47b163a4ab"
],
"Data": {
"Fields": null,
"Name": "2.vIFnH9e2cejXyXs1XMdiyg==|Ee+mdNJsy8ua9zjatSVxug==|oPLHptK5ZTWcTvLPbOxMqgK8c6+N/ASdP/JmzFex3OE=",
"Notes": null,
"Password": "2.JG5vjGkMIaBGTtuhQBNUhA==|m7I75JrwQszyHh8R7Wd7hw==|MLnJxXZpw9Fsky8MDvzvalPFv7mAVwChRx+PbHCRn4Q=",
"PasswordHistory": null,
"PasswordRevisionDate": null,
"Response": null,
"Totp": null,
"Uri": "2.E0orUPHrmXnU+jAnNXScDQ==|2INzrxnKpjHW7nD/XSKLVQ==|Dp5liHqFClKnZZ65Yr6Os9lfNJwvkRNKrvSAiTi04Lk=",
"Uris": [
{
"Match": null,
"Response": null,
"Uri": "2.E0orUPHrmXnU+jAnNXScDQ==|2INzrxnKpjHW7nD/XSKLVQ==|Dp5liHqFClKnZZ65Yr6Os9lfNJwvkRNKrvSAiTi04Lk="
}
],
"Username": "2.z9wIxAMHtJrU0ABvT91ZcA==|vPbfM9cjSXZttdFHcOvRXg==|YJNXaxX5rh/yOn+OycyU/+2agUM0plDtkjzksaj+yXM="
},
"DeletedDate": null,
"Edit": true,
"Favorite": false,
"Fields": null,
"FolderId": "5857eed8-46ed-4295-a62d-73fde176bae1",
"Id": "119bfff1-05c4-4ce8-b761-20a6e44484fb",
"Login": {
"Fields": null,
"Name": "2.vIFnH9e2cejXyXs1XMdiyg==|Ee+mdNJsy8ua9zjatSVxug==|oPLHptK5ZTWcTvLPbOxMqgK8c6+N/ASdP/JmzFex3OE=",
"Notes": null,
"Password": "2.JG5vjGkMIaBGTtuhQBNUhA==|m7I75JrwQszyHh8R7Wd7hw==|MLnJxXZpw9Fsky8MDvzvalPFv7mAVwChRx+PbHCRn4Q=",
"PasswordHistory": null,
"PasswordRevisionDate": null,
"Response": null,
"Totp": null,
"Uri": "2.E0orUPHrmXnU+jAnNXScDQ==|2INzrxnKpjHW7nD/XSKLVQ==|Dp5liHqFClKnZZ65Yr6Os9lfNJwvkRNKrvSAiTi04Lk=",
"Uris": [
{
"Match": null,
"Response": null,
"Uri": "2.E0orUPHrmXnU+jAnNXScDQ==|2INzrxnKpjHW7nD/XSKLVQ==|Dp5liHqFClKnZZ65Yr6Os9lfNJwvkRNKrvSAiTi04Lk="
}
],
"Username": "2.z9wIxAMHtJrU0ABvT91ZcA==|vPbfM9cjSXZttdFHcOvRXg==|YJNXaxX5rh/yOn+OycyU/+2agUM0plDtkjzksaj+yXM="
},
"Name": "2.vIFnH9e2cejXyXs1XMdiyg==|Ee+mdNJsy8ua9zjatSVxug==|oPLHptK5ZTWcTvLPbOxMqgK8c6+N/ASdP/JmzFex3OE=",
"Notes": null,
"Object": "cipher",
"OrganizationId": "2f5c89a5-4b3e-417d-b29a-289d675fc384",
"OrganizationUseTotp": true,
"PasswordHistory": null,
"RevisionDate": "2020-05-10T15:26:43.417248Z",
"Type": 1
}
Do you see something wrong in my encrypt function ? With enckey or mackey ?
i tried to reverse the code of bitwarden browser but didnt find the functions I was interested in
Thanks for your ideas
Best regards
BDO