Skip to content

Instantly share code, notes, and snippets.

@roblabla
Last active September 25, 2021 01:36
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roblabla/4a43d0a5fc3769c815ab to your computer and use it in GitHub Desktop.
Save roblabla/4a43d0a5fc3769c815ab to your computer and use it in GitHub Desktop.
Notes about Dashlane

Login

Dashlane supports three methods of login :

  • UKI : Provide a previously-given unique-key-identifier as the uki parameter. TODO : How do we get UKIs ?
  • Token : Provide a (not-completely) one-time token as the "token" parameter. To generate a token, send a request to /6/authentication/sendtoken with login=email
  • OTP : Probably for Google Authenticator.

Obtaining an UKI : POST requiest to /6/authentification/registeruki with following params :

  • devicename
  • login
  • platform
  • temporary ? (seen value : 0)
  • token
  • uki

Full Backup File

To get the "backup" (AKA the vault), you need to do a request to /12/backup/latest which accepts :

Variable Name Possible values What it does
lock nolock, an OID ?
login your email
needsKey true, false Provides RSA public and private key. Use unknown
sharedLock true, false ?
sharingCapability Number ?
sharingSkipped needsKey,webapp ?
sharingTimestamp Number probably timestamp since last SHARED KEYS sync.
timestamp curr timestamp Timestamp since last sync. Set to 0 to get full backup.
token/otp/uki Used to login the user without sending the master password.
Once you have your hands on the full backup, you'll have to have a lot of fun decoding it.

Steps :

  • base64decode the vault.
  • salt = bytes 0-32
  • If bytes 32-36 === 'KWC3', then :
    • aes is bytes 36-end
    • data is compressed
  • else
    • aes is bytes 32-end
    • data is not compressed
  • Derive a key from the password and salt using PBKDF2, 10204 iterations, 32 bit key length, 'sha1' hash.
  • As if PBKDF2 wasn't enough, derive ANOTHER key using OpenSSL's EVP_BytesToKey, with PBKDF2 key as data, same salt, SHA algorithm, 1 iteration and 256 bit size.
  • If data is compressed
    • Create an AES256-CBC decipher, using pbkdf2 as key and EVP_BytesToKey's IV as IV.
  • else
    • Create an AES256-CBC decipher using the EVP_BytesToKey's key and IV.
  • Decrypt the data using the decipher
  • If data is compressed
    • Remove bytes 0-6 and the last byte (Don't ask me...)
    • Raw Inflate the rest of the byte stream
  • You should now have XML data. Documentation about its format comming soon.

Just a list of the routes we can find from webaccess, on which a lot of this is based.

WS_URL = 'https://ws1.dashlane.com';
var routes = {
  'url-ws-icon-crawler': WS_URL + '/2/iconcrawler/getIcons',
  'url-ws-authentication-exists': WS_URL + '/6/authentication/exists',
  'url-ws-authentication-register': WS_URL + '/6/authentication/register',
  'url-ws-authentication-create': WS_URL + '/6/authentication/create',
  'url-ws-authentication-sendtoken': WS_URL + '/6/authentication/sendtoken',
  'url-ws-authentication-registeruki': WS_URL + '/6/authentication/registeruki',
  'url-ws-authentication-getWebAccessUki': WS_URL + '/6/authentication/getWebAccessUki',
  'url-ws-ukivalidity': WS_URL + '/6/authentication/validity',
  'url-ws-remotedeletion-status': WS_URL + '/2/remotedeletion/status',
  'url-ws-sync-lock': WS_URL + '/12/backup/lock',
  'url-ws-sync-latest': WS_URL + '/12/backup/latest',
  'url-ws-sync-upload': WS_URL + '/12/backup/upload',
  'url-ws-sync-erase': WS_URL + '/12/backup/erase',
  'url-ws-sync-unlock': WS_URL + '/12/backup/unlock',
  'url-ws-credentials-upload': WS_URL + '/1/credentials/sync',
  'url-ws-get-devices': WS_URL + '/1/devices/list',
  'url-ws-device-changename': WS_URL + '/1/devices/changename',
  'url-ws-device-deactivate': WS_URL + '/1/devices/deactivate',
  'url-ws-get-referrers': WS_URL + '/1/invites/history',
  'url-ws-invites-sendInvites': WS_URL + '/1/invites/invite',
  'url-ws-invites-trackShare': WS_URL + '/1/invites/sharing',
  'url-ws-invites-invitelinks': WS_URL + '/1/invites/getSharingLink',
  'url-ws-points-getSummary': WS_URL + '/2/points/getSummary',
  'url-ws-points-buyFeature': WS_URL + '/2/points/buyFeature',
  'url-ws-coupons-getAvailability': WS_URL + '/6/coupons/getAvailability',
  'url-ws-premium-status': WS_URL + '/3/premium/status',
  'url-ws-premium-subscribe': WS_URL + '/3/premium/subscribe',
  'url-ws-premium-updateCardToken': WS_URL + '/3/premium/updateCardToken',
  'url-ws-premium-updatePlan': WS_URL + '/3/premium/updatePlan',
  'url-ws-premium-cancelPlan': WS_URL + '/3/premium/cancelPlan',
  'url-ws-premium-invoices': WS_URL + '/3/premium/invoices',
  'url-ws-premium-coupon-infos': WS_URL + '/3/premium/couponInfos',
  'url-ws-premium-stripePublishableKey': WS_URL + '/3/premium/stripePublishableKey',
  'url-ws-premium-stripePublishableKeys': WS_URL + '/3/premium/stripePublishableKeys',
  'url-ws-premium-getSubscriptionCode': WS_URL + '/3/premium/getSubscriptionCode',
  'url-ws-teamPlans-state': WS_URL + '/1/teamPlans/state',
  'url-ws-teamPlans-addMember': WS_URL + '/1/teamPlans/addMember',
  'url-ws-teamPlans-changeMember': WS_URL + '/1/teamPlans/changeMember',
  'url-ws-teamPlans-removeMember': WS_URL + '/1/teamPlans/removeMember',
  'url-ws-teamPlans-updateBillingAdmin': WS_URL + '/1/teamPlans/updateBillingAdmin',
  'url-ws-teamPlans-updatePaymentMean': WS_URL + '/1/teamPlans/updatePaymentMean',
  'url-ws-supportus-submit': WS_URL + '/1/supportus/submit',
  'url-ws-supportus-getList': WS_URL + '/1/supportus/getList',
  'url-ws-shareditems-getCredential': WS_URL + '/1/securesharing/getCredential',
  'url-ws-contactinfos-get': WS_URL + '/1/contactinfos/get',
  'url-ws-doginvites-sendInvites': WS_URL + '/1/holidaysInvites/invite',
  'url-ws-doginvites-history': WS_URL + '/1/holidaysInvites/history',
  'url-ws-invoices-generate': WS_URL + '/1/invoiceGeneration/generate.pdf'
};

Someone will have to explain to me the logic of the /1, /2, /6... logic.

The transaction list seems to hold a series of actions to apply to the current XML in order to bring it up to date without redownloading the whole vault.

Format details

The transactionList is an array of changes. A change is an object with the following key/value pairs :

Key Value
action Enum[BACKUP_REMOVE,BACKUP_EDIT,?]
type Enum[AUTH_CATEGORY,SECURENOTE_CATEGORY,AUTHENTIFIANT,GENERATED_PASSWORD,SETTINGS,...]
backupDate timestamp
content Encrypted, base64'd content
time timestamp, different from backup date, no idea what it means
objectType 'transaction'
identifier UUID

Building blocks

Everything is inside /root/KWDataList.

Every node inside that list seems to have the following "KWDataItem" keys :

Key Value
Id UUID
LocaleFormat Enum[FR,US,...]
AnonId UUID

Note about UUID : While most generated UUID seem to be in UUIDv1 format (Time+Mac-based), some are in UUIDv4. My guess is, the version used to generate the UUID doesn't matter, so long as no collisions are made.

KWAddress

Key Value
AddressFull
AddressName
Building
City
Country
DigitCode
Door
Floor
LinkedPhone
Receiver
Stairs
State
StateLevel2
StateNumber
StreetName
StreetNumber
StreetTitle
ZipCode

KWEmail

Key Value
Email
EmailName
Type

KWIdentity

Key Value
BirthDate
BirthPlace
FirstName
LastName
MiddleName
Pseudo
Title

KWPersonalWebsite

Key Value
Name
Website

KWPhone

Key Value
Number
NumberInternational
NumberNational
PhoneName
Type PHONE_TYPE_MOBILE,

KWIDCard

Key Value
DateOfBirth
DeliveryDate YEAR-M-D (Depends on locale ?)
ExpireDate YEAR-M-D (^^)
Fullname
LinkedIdentity UUID
Number
Sex NO_TYPE,

KWPassport

Key Value
DateOfBirth
DeliveryDate
DeliveryPlace
ExpireDate
Fullname
LinkedIdentity UUID
Number
Sex

KWSocialSecurityStatement

Key Value
DateOfBirth
LinkedIdentity
Sex
SocialSecurityFullname
SocialSecurityNumber

KWPaymentMean_paypal

Key Value
Login
Name
Password
Type PAYMENT_TYPE_PAYPAL,

KWPaymentType_creditCard

Key Value
Bank
CardNumber
CardNumberLastDigits Last 4 digits of CardNumber
Color ENUM[Gold,...]
ExpireMonth
ExpireYear
IssueNumber
LinkedBillingAddress UUID
Name
OwnerName
SecurityCode
StartMonth
StartYear
Type ENUM[PAYMENT_TYPE_MASTERCARD]

KWSecureNote

No LocaleFormat

Key Value
Category UUID
Content
CreationDate timestamp
Secured true/false
Title
Type ?? Seen value : RED. Potential ENUM
UpdateTime timestamp

KWSecureNoteCategory

No LocaleFormat

Key Value
CategoryName

KWDataChangeHistory

This is a weird one. To document later.

KWAuthCategory

Key Value
CategoryName

KWAuthentifiant

Key Value
AutoLogin true/false
Category KWAuthCategory.Id
Checked true/false TODO : Document meaning
CreationDatetime timestamp
Email
LastUse timestamp
Login
ModificationDatetime timestamp
Note
NumberUse
Password
SecondaryLogin
Status ENUM[ACCOUNT_NOT_VERIFIED]
Strength
SubdomainOnly true/false
Title
TrustedUrlGroup A KWDataCollection containing 'TrustedUrl' and 'TrustedUrlExpire'
Url
UseFixedUrl true/false

KWGeneratedPassword

Key Value
AuthId ?
Domain
GeneratedDate timestamp
Password

KWPurchaseCategory

Key Value
CategoryName

KWPurchasePaidBasket

Key Value
AlreadyClient true/false
Articles Collection of KWPurchaseTrueArticle
AutoTitle
BillingAddressDescription
Category
Currency ENUM[US_DOLLAR,]
DeliveryAddressDescription
DeliveryAddressName
DeliveryType
FullScreenFiles Collection of KWPurchaseConfirmation
MerchantDomain
PaymentMeanDescription
PaymentMeanName
PurchaseDate
TotalAmount

KWPurchaseTrueArticle

Key Value
Libelle
Picture
Price
Quantity
Type

KWPurchaseConfirmation

Key Value
Index
Path
Url

KWMerchant

Key Value
Domain
Logo
Title
Website
@kholia
Copy link

kholia commented Sep 23, 2021

Could you provide some sort of POC code, or a clearer explanation, so that I can follow along?

https://github.com/openwall/john/blob/bleeding-jumbo/src/dashlane_fmt_plug.c

@roblabla
Copy link
Author

roblabla commented Sep 24, 2021

Oh damn, I wish github had gist notifications back in 2019. I've written some POC code back when I worked on this at https://github.com/RedImpala/RedImpalaLib-JS/blob/master/dashlane.js#L19. That said, I have no clue if that code still works, or if dashlane changed their system since then.

@kholia
Copy link

kholia commented Sep 25, 2021

Dashlane uses newer file formats and crypto schemes now.

For KDF, it uses Argon2. I am trying to reverse-engineer the exact crypto details for the newer Dashlane file formats (.aes, .dash).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment