· 6 years ago · Jun 02, 2019, 08:53 PM
1package ru.sbi.android.i_credential
2
3import android.annotation.TargetApi
4import android.content.SharedPreferences
5import android.os.Build
6import android.security.keystore.KeyGenParameterSpec
7import android.security.keystore.KeyProperties
8import android.util.Base64
9import ru.surfstudio.android.dagger.scope.PerApplication
10import ru.surfstudio.android.logger.Logger
11import ru.surfstudio.android.shared.pref.NO_BACKUP_SHARED_PREF
12import ru.surfstudio.android.shared.pref.SettingsUtil
13import java.nio.charset.StandardCharsets
14import java.security.KeyStore
15import java.security.MessageDigest
16import java.security.SecureRandom
17import javax.crypto.Cipher
18import javax.crypto.KeyGenerator
19import javax.crypto.SecretKey
20import javax.crypto.spec.IvParameterSpec
21import javax.crypto.spec.PBEKeySpec
22import javax.crypto.spec.SecretKeySpec
23import javax.inject.Inject
24import javax.inject.Named
25
26/**
27 * Хранилище на основе [KeyStore] в котором хранятся токены для запросов к серверу
28 */
29@PerApplication
30class TokensStorage @Inject constructor(
31 @Named(NO_BACKUP_SHARED_PREF) private val sharedPreferences: SharedPreferences
32) {
33
34 companion object {
35 private const val ANDROID_KEYSTORE = "AndroidKeyStore"
36 // private const val BOUNCY_CASTLE = "BouncyCastle"
37// private const val BLOWFISH_KEY_LENGTH = 256
38 private const val KEY_DIGEST_ALGORITHM = "SHA-256"
39 // private const val BLOWFISH = "Blowfish"
40 private const val AES = "AES"
41 private const val DEFAULT_CIPHER_TRANSFORMATION = "AES/CBC/PKCS7Padding"
42
43 private const val ALIAS_ACCESS_TOKEN = "ARNDK"
44 private const val ALIAS_REFRESH_TOKEN_PIN = "REFPIN"
45 private const val ALIAS_REFRESH_TOKEN_FINGER = "REFFIN"
46
47 private const val ALIAS_PIN_CODE = "PNRDNK"
48
49 private const val DEFAULT_KEY_ALGORITHM = "PBKDF2WithHmacSHA1"
50 // private const val DEFAULT_SALT_SIZE = 16
51 private const val KEY_LENGTH = 256
52 private const val ITERATION_COUNT = 16384
53
54 private const val IV_SIZE_IN_BYTES = 16
55 }
56
57 fun savePinCode(pin: String) {
58 saveString(ALIAS_PIN_CODE, pin)
59 }
60
61 fun saveAccessToken(access: String) {
62 saveString(ALIAS_ACCESS_TOKEN, access)
63 }
64
65 fun saveRefreshTokenWithPinEncryption(refresh: String, pinCode: String? = null) {
66 saveString(ALIAS_REFRESH_TOKEN_PIN, refresh, pinCode)
67 }
68
69 fun saveRefreshTokenWithFingerprintEncryption(refresh: String, fingerPrint: String? = null) {
70 saveString(ALIAS_REFRESH_TOKEN_FINGER, refresh, fingerPrint)
71 }
72
73 fun getAccessToken(): String? {
74 return getString(ALIAS_ACCESS_TOKEN)
75 }
76
77 fun getRefreshTokenPin(): String? {
78 return getString(ALIAS_REFRESH_TOKEN_PIN)
79 }
80
81 fun getRefreshTokenFingerPrint(): String? {
82 return getString(ALIAS_REFRESH_TOKEN_FINGER)
83 }
84
85 fun pinIsValid(pinFromInput: String): Boolean {
86 val decryptedPin = getString(ALIAS_PIN_CODE)
87 return pinFromInput == decryptedPin
88 }
89
90 /**
91 * Сохраняет произвольную строку в настройки, предварительно шифруя ее с помощью ключа.
92 * Последний используемый ключ сохраняется в KeyStore.
93 */
94 private fun saveString(alias: String, str: String, encryptionKey: String? = null) {
95
96 val keyStore = getTokensKeyStore()
97
98 val ivBytes = ByteArray(IV_SIZE_IN_BYTES)
99
100 SecureRandom().nextBytes(ivBytes)
101
102 val iv = IvParameterSpec(ivBytes)
103
104 val cipher = Cipher.getInstance(DEFAULT_CIPHER_TRANSFORMATION)
105
106 when {
107 encryptionKey != null -> {
108 val key = generateSecretKey(alias, keyStore)
109 keyStore.setKeyEntry(alias, key, null, arrayOf())
110 cipher.init(Cipher.ENCRYPT_MODE, key, iv)
111 }
112 keyStore.containsAlias(alias) -> {
113 val key = keyStore.getKey(alias, null)
114 cipher.init(Cipher.ENCRYPT_MODE, key, iv)
115 }
116 else -> {
117 setRandomKeyForAlias(alias, keyStore)
118 val key = keyStore.getKey(alias, null)
119 cipher.init(Cipher.ENCRYPT_MODE, key, iv)
120 }
121 }
122
123 val enc =cipher.doFinal(str.toBytes())
124
125 Logger.d("*** alias = $alias;;; encrypted (saveString) = ${enc.toUtf8String()}")
126
127
128 val toSave = (ivBytes + enc).toUtf8String()
129 SettingsUtil.putString(sharedPreferences, alias, toSave)
130 }
131
132 /**
133 * Извлекает произвольную строку из настроек, расшифровывая ее существующим ключем
134 */
135 private fun getString(alias: String): String? {
136 var prefValue = SettingsUtil.getString(sharedPreferences, alias)
137
138 if(prefValue.isBlank()) return null
139
140 val ivBytes = prefValue.toBytes().take(IV_SIZE_IN_BYTES).toByteArray()
141
142 val iv = IvParameterSpec(ivBytes)
143
144 prefValue = prefValue.substring(IV_SIZE_IN_BYTES)
145
146 val keyStore = getTokensKeyStore()
147
148 if (!keyStore.containsAlias(alias)) return null
149
150 val key = keyStore.getKey(alias, null)
151 val cipher = Cipher.getInstance(DEFAULT_CIPHER_TRANSFORMATION)
152
153 cipher.init(Cipher.DECRYPT_MODE, key, iv)
154
155 val enc = prefValue.toBytes().drop(IV_SIZE_IN_BYTES).toByteArray()
156
157 Logger.d("*** alias = $alias;;; encrypted (getString) = ${enc.toUtf8String()}")
158
159
160 return cipher.doFinal(enc).toUtf8String()
161 }
162
163 fun clear() {
164 val keyStore = getTokensKeyStore()
165
166 keyStore.deleteEntry(ALIAS_PIN_CODE)
167 keyStore.deleteEntry(ALIAS_ACCESS_TOKEN)
168 keyStore.deleteEntry(ALIAS_REFRESH_TOKEN_PIN)
169 keyStore.deleteEntry(ALIAS_REFRESH_TOKEN_FINGER)
170 }
171
172 private fun getSecretKeySpec(stringKey: String): SecretKeySpec {
173 return SecretKeySpec(stringKey.toByteArray(), AES)
174// return SecretKeySpec(stringKey.toByteArray(), BLOWFISH)
175 }
176
177 @TargetApi(Build.VERSION_CODES.M)
178 private fun setRandomKeyForAlias(alias: String, keyStore: KeyStore) {
179
180// val secretKey = generateSecretKey(bytes.toUtf8String(), SecurityUtils.generateSalt())
181
182 val secretKey = generateSecretKey(alias, keyStore)
183
184 keyStore.setKeyEntry(alias, secretKey, null, arrayOf())
185
186
187// keyStore.setEntry(alias, KeyStore.SecretKeyEntry(secretKey), prot)
188 }
189
190
191 @TargetApi(Build.VERSION_CODES.M)
192 private fun generateSecretKey(alias: String, keyStore: KeyStore): SecretKey {
193 val builder = getKeySpecBuilder(alias)
194
195// val keyGenerator = KeyGenerator.getInstance(BLOWFISH, keyStore.provider)
196
197// Logger.d("*** keystore provider = ${keyStore.provider}")
198
199
200// val ivSpec = IvParameterSpec()
201
202 val secureRandom = SecureRandom()
203 val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStore.provider)
204 .apply {
205 init(builder.build(), secureRandom) // TODO Добавить сюда secure random
206 }
207
208
209// keyGenerator.init(BLOWFISH_KEY_LENGTH, keyStore.provider)
210
211 val secretKey = keyGenerator.generateKey()
212
213 val bytes = ByteArray(512)
214
215 secureRandom.nextBytes(bytes)
216
217 return secretKey
218 }
219
220// // TODO определить сходство
221// private fun generateSecretKey(keyString: String): SecretKey {
222// val salt = SecurityUtils.generateSalt()
223// return generateSecretKey(keyString, salt)
224// }
225
226 // TODO подумать как понизить версию
227 @TargetApi(Build.VERSION_CODES.M)
228 private fun getKeySpecBuilder(keyAlias: String): KeyGenParameterSpec.Builder {
229
230// val secureRandom = SecureRandom()
231//
232// val bytes = ByteArray(8*32) // предположим что пароль 32 символа
233
234 return KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
235 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
236 .setKeySize(KEY_LENGTH)
237 .setRandomizedEncryptionRequired(false)
238// .setAlgorithmParameterSpec(PBEParameterSpec(SecurityUtils.generateSalt(), ITERATION_COUNT))
239 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
240 .setUserAuthenticationRequired(false)
241 .setUserAuthenticationValidityDurationSeconds(-1)
242 }
243
244// @TargetApi(Build.VERSION_CODES.M)
245// private fun getKeyProtection(): KeyProtection {
246// return KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
247// .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
248// .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
249// .build()
250// }
251
252 private fun getHash(data: String): String {
253 val digest = MessageDigest.getInstance(KEY_DIGEST_ALGORITHM)
254 val dataBytes = data.toBytes()
255 return digest.digest(dataBytes).toString()
256 }
257
258// private fun String.toBytes(): ByteArray {
259// return Base64.encode(this, Base64.DEFAULT)
260// }
261
262// private fun ByteArray.toUtf8String(): String {
263// return Base64.encodeToString(this, Base64.DEFAULT)
264// }
265
266// private fun String.toBytes(): ByteArray = Base64.decode(this, Base64.NO_WRAP)
267//
268// private fun ByteArray.toUtf8String(): String = Base64.encodeToString(this, Base64.NO_WRAP)
269
270 private fun String.toBytes(): ByteArray {
271 return this.toByteArray(StandardCharsets.US_ASCII)
272 }
273
274 private fun ByteArray.toUtf8String(): String {
275 return this.toString(StandardCharsets.US_ASCII)
276 }
277
278 private fun getTokensKeyStore(): KeyStore {
279 return KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
280 }
281
282// private fun generateSecretKey(sign: String, salt: ByteArray): SecretKey {
283// return SecretKeyFactory
284// .getInstance(DEFAULT_KEY_ALGORITHM)
285// .generateSecret(getSpec(sign, salt))
286// }
287
288 private fun getSpec(sign: String, salt: ByteArray): PBEKeySpec {
289 return PBEKeySpec(sign.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH)
290 }
291}