[BITWARDEN][API] /api/ciphers/create - Browser

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

I’m trying to re-use some functionality from "[Bitwarden API Overview] of rubywarden project which is a Ruby API for Bitwarden.

I created an entry in Bitwarden with a /api/ciphers/create route.

I can create the entry with organization, folder but no data is posted.

In my post data, I should encrypt data with a specific format.

I used the Bitwarden browser plugin with Burp to check routes and data being posted. I found that my data are not well encrypted like Bitwarden wanted but I don’t know what is wrong.

Here is the code to generate the key and encrypt data:

There are two functions at the end, you have to enter your url of Bitwarden Server and also your account (email + password). You have also to enter the url of bitwarden to the header Host of the new_item function (request[‘Host’] = “URL_BITWARDEN”)

#############
# Bitwarden - API client ruby
#############

# Script to create/delete bitwarden entry via API
#https://github.com/jcs/rubywarden/blob/master/API.md

require 'pbkdf2'
require 'base64'
require 'net/http'
require 'uri'
require 'json'
require 'cgi'

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

def cipherString(enctype, iv, ct, mac)
  [ enctype.to_s + "." + iv, ct, mac ].reject{|p| !p }.join("|")
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

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

# base64-encode a wrapped, stretched password+salt for signup/login
def hashedPassword(password, username, kdf_iterations)
  key = makeKey(password, username, kdf_iterations)
  Base64.strict_encode64(PBKDF2.new(:password => key, :salt => password,
    :iterations => 1, :key_length => 256/8,
    :hash_function => OpenSSL::Digest::SHA256).bin_string)
end

# compare two hmacs, with double hmac verification
# https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
def macsEqual(macKey, mac1, mac2)
  hmac1 = OpenSSL::HMAC.digest(OpenSSL::Digest.new("SHA256"), macKey, mac1)
  hmac2 = OpenSSL::HMAC.digest(OpenSSL::Digest.new("SHA256"), macKey, mac2)
  return hmac1 == hmac2
end

# decrypt a CipherString and return plaintext
def decrypt(str, key, macKey)
  if str[0].to_i != 2
    raise "implement #{str[0].to_i} decryption"
  end

  # AesCbc256_HmacSha256_B64
  iv, ct, mac = str[2 .. -1].split("|", 3)

  iv = Base64.decode64(iv)
  ct = Base64.decode64(ct)
  mac = Base64.decode64(mac)

  cmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("SHA256"), macKey, iv + ct)
  if !macsEqual(macKey, mac, cmac)
    raise "invalid mac"
  end

  cipher = OpenSSL::Cipher.new "aes-256-cbc-hmac-sha256"
  cipher.decrypt
  cipher.iv = iv
  cipher.key = key
  pt = cipher.update(ct)
  pt << cipher.final
  pt
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 get_iterations(host, username)
  uri = URI.parse("#{host}/api/accounts/prelogin")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/json"
  request.body = "{
      \"email\": \"#{username}\"
  }"

  req_options = {
    use_ssl: uri.scheme == "https"
  }

  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end

  case response
  when Net::HTTPSuccess
    return json = JSON.parse(response.body)
  else
    return 1
  end
end
#### API FUNCTIONS ####

#############
# Signup
#############
#Collect an e-mail address and master password, calculate internalKey, masterPasswordHash, and the $key CipherString from the two values:
def signup(host, username ,password, iterations)
  internalKey = makeKey(password, username.downcase, iterations)
  masterPasswordHash = hashedPassword(password, username, iterations)
  key = makeEncKey(internalKey)

  uri = URI.parse("#{host}/api/accounts/register")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/json"
  request.body = "{
      \"name\": nil,
      \"email\": \"#{username}\",
      \"masterPasswordHash\": \"#{masterPasswordHash}\",
      \"masterPasswordHint\": nil,
      \"key\": \"#{key}\",
      \"kdf\": 0,
      \"kdfIterations\": #{iterations},
  }"

  req_options = {
    use_ssl: uri.scheme == "https",
  }

  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end
end

#############
# Login
#############
# Collect an e-mail address and master password, and issue a POST to $baseURL/accounts/prelogin to determine the KDF iterations for the given e-mail address:
def login(host, username, password)
  json = get_iterations(host, username)
  if json.nil? || json.empty?
    return 1
  end

  internalKey = makeKey(password, username.downcase, json["KdfIterations"])
  masterPasswordHash = hashedPassword(password, username, json["KdfIterations"])

  masterPasswordHash_escape = CGI.escape "#{masterPasswordHash}"

  uri = URI.parse("#{host}/identity/connect/token")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/x-www-form-urlencoded; charset=utf-8"
  request["Device_Type"] = "10"
  request["Origin"] = "#{host}"
  request.body = "grant_type=password&username=#{username}&password=#{masterPasswordHash_escape}&scope=api%20offline_access&client_id=linux&deviceType=10&deviceIdentifier=test&deviceName=linux"

  req_options = {
    use_ssl: uri.scheme == "https",
  }
  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end

  case response
  when Net::HTTPSuccess
    json = JSON.parse(response.body)
    access_token = json["access_token"]
    system("echo", access_token)
    return access_token
  else
    return 1
  end
end

#############
# new_item
#############
# Collect an e-mail address and master password, and issue a POST to $baseURL/accounts/prelogin to determine the KDF iterations for the given e-mail address:
def new_item(host, username, password, data, access_token)

  json = get_iterations(host, username)
  if json.nil? || json.empty?
    return 1
  end

  masterkey = makeKey(password, username, json["KdfIterations"])
  cipherString = makeEncKey(masterkey)

  mac = getmac(cipherString, masterkey)
  enckey = getenckey(cipherString, masterkey)

  json_data = JSON.parse(data)
  enc_name = encrypt(json_data["name"], enckey, mac)
  enc_username = encrypt(json_data["username"], enckey, mac)
  enc_password = encrypt(json_data["password"], enckey, mac)
  enc_url = encrypt(json_data["url"], enckey, mac)
  folderId = json_data["folderId"]
  organizationId = json_data["organizationId"]
  collectionIds = json_data["collectionIds"]

  uri = URI.parse("#{host}/api/ciphers/create")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/json; charset=utf-8"
  request["Authorization"] = "Bearer #{access_token}"
  request.body = JSON.dump({
    "cipher": {
        "type": 1,
        "folderId": "#{folderId}",
        "organizationId": "#{organizationId}",
        "name": "#{enc_name}",
        "notes": nil,
        "favorite": false,
        "login": {
            "response": nil,
            "uris": [
                {
                    "response": nil,
                    "match": nil,
                    "uri": "#{enc_url}"                }
            ],
            "username": "#{enc_username}",
            "password": "#{enc_password}",
            "passwordRevisionDate": nil,
            "totp": nil
        }
    },
    "collectionIds": [
      "#{collectionIds}"
    ]
  })

  request['Content-Length'] = request.body.length
  request['Host'] = "URL_BITWARDEN"
  req_options = {
    use_ssl: uri.scheme == "https",
  }
  request.each_header do |key, value|
    puts "\t#{key}: #{value}"
  end  
  puts request.body

  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end

  puts response.body
  case response
  when Net::HTTPSuccess
    system('echo', "ok")
    return 0
  else
    system('echo', "nok")
    return 1
  end

end

access_token = login('URL_BITWARDEN', 'EMAIL', 'PASSWORD')
data = '{"name":"test","username":"test","password":"test","url":"test","folderId":"5857eed8-46ed-4295-a62d-73fde176bae1","organizationId":"2f5c89a5-4b3e-417d-b29a-289d675fc384","collectionIds":"6f43850b-b547-452b-a646-9d47b163a4ab"}'
new_item('URL_BITWARDEN', 'EMAIL', 'PASSWORD', data, access_token)

Example of POST data:

This is the 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"
    ]
}

This is the 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 for “Bitwarden Browser Extension” but didn’t find the functions I wanted.

EDIT: Refunt the question

If you need more information, feel free to ask

BDO