Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Excerpt

JIRA gives you the choice of storing user records internally, or delegating to an external 'User Directory' , like Active Directory, LDAP or Atlassian Crowd.

Many smaller orgs start off with internal user records, but later want to migrate users to LDAP , for ease of management, or to allow authentication with non-Atlassian LDAP-aware systems.

Generating LDAP (LDIF) records from JIRA's cwd_* tables is not hard, but how about password hashes?

On this page we'll describe how to convert JIRA credential hashes:

Code Block
{PKCS5S2}U48fu6LonjKCk0VmHPsgLrKf1/i1o/wxLXblOTa6P8eXvvJTU4iRb0fpRlO3xA0J

into a format understandable by OpenLDAP (with the  pw-pbkdf2  module loaded):

Code Block
{PBKDF2}10000$cjsPF6FcSW9CDwmpREtZog$qWi06T.6SSapuTtDsFn/2DPacsc

This will let you migrate user records from Jira into LDAP without forcing everyone to reset their password.

...

Code Block
redradish_jira=> select * from cwd_user where user_name='jturner';
┌─[ RECORD 1 ]────────┬───────────────────────────────────────────────────────────────────────────┐
│ id                  │ 10000                                                                     │
│ directory_id        │ 1                                                                         │
│ user_name           │ jturner                                                                   │
│ lower_user_name     │ jturner                                                                   │
│ active              │ 1                                                                         │
│ created_date        │ 2013-09-02 18:14:34.078712+10                                             │
│ updated_date        │ 2018-02-23 10:33:48.481+11                                                │
│ first_name          │ Jeff                                                                      │
│ lower_first_name    │ jeff                                                                      │
│ last_name           │ Turner                                                                    │
│ lower_last_name     │ turner                                                                    │
│ display_name        │ Jeff Turner                                                               │
│ lower_display_name  │ jeff turner                                                               │
│ email_address       │ jeff@redradishtech.com                                                    │
│ lower_email_address │ jeff@redradishtech.com                                                    │
│ credential          │ {PKCS5S2}U48fu6LonjKCk0VmHPsgLrKf1/i1o/wxLXblOTa6P8eXvvJTU4iRb0fpRlO3xA0J │
│ deleted_externally  │ ␀                                                                         │
│ external_id         │ a330dede-18f8-4745-ac8d-d2ec2bcabedc                                      │
└─────────────────────┴───────────────────────────────────────────────────────────────────────────┘


Atlassian's PKCS5S2 format

What exactly is that PKCS5S2 format JIRA uses for password hashes?

'PKCS5S2' refers to "PKCS #5: Password-Based Cryptography Specification Version 2.0", a document available in RFC form which provides "recommendations for the implementation of password-based cryptography" . The recommendations include the use of the PBKDF2 'key derivation function', of which HMAC-SHA-1 is an example. Apparently.

Anyhow, the The format is succinctly explained in the passlib.hash.atlassian_pbkdf2_sha1  Python library's docs:

  • generates generate a random 16-byte salt
  • feeds the salt plus password into our PBKDF2 function, which applies a hash (HMAC-SHA1) 10,000 times, yielding a a 32-byte hash
  • concatenates salt and hash, and base64-encodes them

...

Code Block
$ salt="$(echo -n "$credential" | base64 -d | head -c16)"
$ hash="$(echo -n "$credential" | base64 -d | tail -c32)"

OpenLDAP's PBKDF2 Support

OpenLDAP supports PBKDF2. The problem is that its format is different:

...

We know the iteration count (10000). We know the salt, and we know the hash (derived key). We just need to reorder the elements.

Also, what is "Adapted adapted Base64"? Per the passlib docs it is , per the passlib docs,just a shortened base64 format which trims the padding (appearing as '=' at the end of base64-encoded strings), and uses '.' characters instead of '+'.We can define this as a bash function:

Code Block
$ ab64encode() { python3 -c 'import sys; from passlib.utils.binary import *; print(ab64_encode(sys.stdin.buffer.read()).decode("utf-8"))'; }
$ echo foo | base64        # regular base64
Zm9vCg==
$ echo foo | ab64encode    # adapted base64
Zm9vCg

Now we have everything we need to write a conversion function:

Code Block
function atlassian_to_pbkdf2()
{
  ab64encode() { python3 -c 'import sys; from passlib.utils.binary import *; print(ab64_encode(sys.stdin.buffer.read()).decode("utf-8"))'; }
  local credential="$1"
  credential="${credential#'{PKCS5S2}'}"
  salt="$(echo -n "$credential" | base64 -d | head -c16 | ab64encode)"
  hash="$(echo -n "$credential" | base64 -d | tail -c32 | ab64encode)"
  printf "{PBKDF2}%d$%s$%s" 10000 "$salt" "$hash" | head -c64
  echo
}

A sample run:

Code Block
$ atlassian_to_pbkdf2 {PKCS5S2}U48fu6LonjKCk0VmHPsgLrKf1/i1o/wxLXblOTa6P8eXvvJTU4iRb0fpRlO3xA0J
{PBKDF2}10000$U48fu6LonjKCk0VmHPsgLg$sp/X.LWj/DEtduU5Nro/x5e.8lN

Testing that this is correct is a bit tricky. Per the advice for OpenLDAP PBKDF2 page, the best way is probably to set this password in your /etc/ldap/slapd.conf:

Code Block
moduleload      pw-pbkdf2.so
...
rootdn          "cn=admin,dc=redradishtech,dc=com"
rootpw          {PBKDF2}10000$U48fu6LonjKCk0VmHPsgLg$sp/X.LWj/DEtduU5Nro/x5e.8lN


What about

https://git.openldap.org/openldap/openldap/-/tree/master/contrib/slapd-modules/passwd/sha2