AccessTokenEncryptor.coffee 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. crypto = require('crypto')
  2. ALGORITHM = 'aes-256-ctr'
  3. keyFn = (password, salt, callback)->
  4. return crypto.pbkdf2(password, salt, 10000, 64, 'sha1', callback)
  5. keyFn32 = (password, salt, keyLength, callback)->
  6. return crypto.pbkdf2(password, salt, 10000, 32, 'sha1', callback)
  7. class AccessTokenEncryptor
  8. constructor: (settings) ->
  9. @settings = settings
  10. @cipherLabel = @settings.cipherLabel
  11. throw Error("cipherLabel must not contain a colon (:)") if @cipherLabel?.match(/:/)
  12. @cipherPassword = @settings.cipherPasswords[@cipherLabel]
  13. throw Error("cipherPassword not set") if not @cipherPassword?
  14. throw Error("cipherPassword too short") if @cipherPassword.length < 16
  15. encryptJson: (json, callback) ->
  16. string = JSON.stringify(json)
  17. crypto.randomBytes 32, (err, bytes) =>
  18. return callback(err) if err
  19. salt = bytes.slice(0, 16)
  20. iv = bytes.slice(16, 32)
  21. keyFn32 @cipherPassword, salt, 32, (err, key) =>
  22. if err?
  23. logger.err err:err, "error getting Fn key"
  24. return callback(err)
  25. cipher = crypto.createCipheriv(ALGORITHM, key, iv)
  26. crypted = cipher.update(string, 'utf8', 'base64') + cipher.final('base64')
  27. callback(null, "#{@cipherLabel}:#{salt.toString('hex')}:#{crypted}:#{iv.toString('hex')}")
  28. decryptToJson: (encryptedJson, callback) ->
  29. [label, salt, cipherText, iv] = encryptedJson.split(':', 4)
  30. password = @settings.cipherPasswords[label]
  31. return callback(new Error("invalid password")) if not password? or password.length < 16
  32. if iv
  33. @decryptToJsonV2(password, salt, cipherText, iv, callback)
  34. else
  35. @decryptToJsonV1(password, salt, cipherText, callback)
  36. decryptToJsonV1: (password, salt, cipherText, callback) ->
  37. keyFn password, Buffer.from(salt, 'hex'), (err, key) =>
  38. if err?
  39. logger.err err:err, "error getting Fn key"
  40. return callback(err)
  41. decipher = crypto.createDecipher(ALGORITHM, key)
  42. dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8')
  43. try
  44. json = JSON.parse(dec)
  45. catch e
  46. return callback(new Error("error decrypting token"))
  47. callback(null, json, true)
  48. decryptToJsonV2: (password, salt, cipherText, iv, callback) ->
  49. keyFn32 password, Buffer.from(salt, 'hex'), 32, (err, key) =>
  50. if err?
  51. logger.err err:err, "error getting Fn key"
  52. return callback(err)
  53. decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'))
  54. dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8')
  55. try
  56. json = JSON.parse(dec)
  57. catch e
  58. return callback(new Error("error decrypting token"))
  59. callback(null, json)
  60. module.exports = AccessTokenEncryptor