comparison src/main/kotlin/name/blackcap/passman/Encryption.kt @ 0:a6cfdffcaa94

Initial commit, incomplete but it runs sorta.
author David Barts <n5jrn@me.com>
date Sun, 11 Sep 2022 16:11:37 -0700
parents
children 711cc42e96d7
comparison
equal deleted inserted replaced
-1:000000000000 0:a6cfdffcaa94
1 package name.blackcap.passman
2
3 import java.nio.ByteBuffer
4 import java.nio.CharBuffer
5 import java.nio.charset.Charset
6 import java.nio.charset.StandardCharsets
7 import java.security.SecureRandom
8 import javax.crypto.Cipher
9 import javax.crypto.SecretKey
10 import javax.crypto.SecretKeyFactory
11 import javax.crypto.spec.IvParameterSpec
12 import javax.crypto.spec.PBEKeySpec
13 import javax.crypto.spec.SecretKeySpec
14 import javax.security.auth.Destroyable
15
16 class Encryption(passwordIn: CharArray, saltIn: ByteArray) : Destroyable {
17 private companion object {
18 const val ITERATIONS = 390000
19 const val IV_LENGTH = 16
20 const val KEY_LENGTH = 256
21 const val ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding"
22 const val KEY_ALGORITHM = "AES"
23 const val SECRET_KEY_FACTORY = "PBKDF2WithHmacSHA256"
24 val CHARSET : Charset = StandardCharsets.UTF_8
25 val ZERO_IV = ByteArray(IV_LENGTH).apply { clear() }
26 }
27
28 private val secretKey = getSecretKey(passwordIn, saltIn)
29 private val secureRandom = SecureRandom()
30
31 fun encrypt(plaintext: CharArray): ByteArray {
32 val iv = ByteArray(IV_LENGTH).also { secureRandom.nextBytes(it) }
33 return encrypt(plaintext, iv)
34 }
35
36 private fun encrypt(plaintext: CharArray, iv: ByteArray): ByteArray {
37 val cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM)
38 cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivFactory(iv))
39 val inBuffer = CHARSET.encode(CharBuffer.wrap(plaintext))
40 val outBuffer = ByteBuffer.allocate(cipher.getOutputSize(inBuffer.limit()) + IV_LENGTH)
41 outBuffer.put(iv)
42 cipher.doFinal(inBuffer, outBuffer)
43 return outBuffer.array()
44 }
45
46 fun encryptFromString(plaintext: String): ByteArray = encrypt(plaintext.toCharArray())
47
48 fun encryptFromString0(plaintext: String): ByteArray =
49 encrypt(plaintext.toCharArray(), ZERO_IV)
50
51 fun decrypt(ciphertext: ByteArray): CharArray {
52 val cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM)
53 cipher.init(Cipher.DECRYPT_MODE, secretKey, ivFactory(ciphertext))
54 val bytes = cipher.doFinal(ciphertext, IV_LENGTH, ciphertext.size - IV_LENGTH)
55 val charBuffer = CHARSET.decode(ByteBuffer.wrap(bytes))
56 bytes.clear()
57 val ret = CharArray(charBuffer.limit())
58 charBuffer.run {
59 rewind()
60 get(ret)
61 zero()
62 }
63 return ret
64 }
65
66 fun decryptToString(ciphertext: ByteArray): String = String(decrypt(ciphertext))
67
68 override fun destroy() {
69 secretKey.destroy()
70 }
71
72 override fun isDestroyed(): Boolean {
73 return secretKey.isDestroyed
74 }
75
76 protected fun finalize() {
77 destroy()
78 }
79
80 private fun getSecretKey(password: CharArray, salt: ByteArray): SecretKey {
81 val factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY)
82 val spec = PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH)
83 return SecretKeySpec(factory.generateSecret(spec).encoded, KEY_ALGORITHM)
84 }
85
86 private fun ivFactory(ciphertext: ByteArray) = IvParameterSpec(ciphertext, 0, IV_LENGTH)
87 }
88
89 fun ByteArray.clear() = indices.forEach { this[it] = 0 }
90
91 fun CharArray.clear() = indices.forEach { this[it] = '\u0000' }
92
93 fun CharBuffer.zero() {
94 clear()
95 array().clear()
96 }