Android AES加密解密工具类

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android AES加密解密工具类相关的知识,希望对你有一定的参考价值。

一个用于android AES加密解密的工具类,记录一下。。。

import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import androidx.annotation.RequiresApi
import java.io.*
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec

@RequiresApi(Build.VERSION_CODES.M)
class CryptoManager 
    companion object 
        private const val IV_BLOCK_SIZE = 16
        private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
        private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
        private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
        private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
        private const val KeyStoreType = "AndroidKeyStore"
        private const val KEY_ALIAS = "SecretKeyAlias"

        private val cipher = Cipher.getInstance(TRANSFORMATION) //创建密码器

        fun encrypt(encryptBytes: ByteArray): ByteArray?
            try 
                cipher.init(Cipher.ENCRYPT_MODE, getKey()) //用密钥初始化Cipher对象
                val final = cipher.doFinal(encryptBytes)
                return cipher.iv + final  // iv占前16位,加密后的数据占后面
             catch (e: Exception) 
                e.printStackTrace()
            
            return null
        

        fun decrypt(decryptBytes: ByteArray): ByteArray? 
            try 
                val iv = decryptBytes.copyOfRange(0, IV_BLOCK_SIZE) // 先取出IV
                val decryptData = decryptBytes.copyOfRange(IV_BLOCK_SIZE, decryptBytes.size) // 取出加密后的数据
                cipher.init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv))
                return cipher.doFinal(decryptData)
             catch (e: Exception) 
                e.printStackTrace()
            
            return null
        
		// type设为"AndroidKeyStore"使用Android专门提供的密钥存储系统,可以根据别名获取key, 类似于sp
        private val keyStore = KeyStore.getInstance(KeyStoreType).apply  load(null) 

        private fun getKey(): SecretKey 
            val existingKey = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.SecretKeyEntry
            return existingKey?.secretKey ?: generateKey()
        

        private fun generateKey(): SecretKey 
            return KeyGenerator.getInstance(ALGORITHM, KeyStoreType).apply 
                init(
                    KeyGenParameterSpec.Builder(
                        KEY_ALIAS,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                    )
                    .setBlockModes(BLOCK_MODE)
                    .setEncryptionPaddings(PADDING)
                    .setUserAuthenticationRequired(false)
                    .setRandomizedEncryptionRequired(true)
                    .build()
                )
            .generateKey()
        

        /**
         * 将 text 加密
         */
        fun encrypt(text: String): String? 
            val encryptedBytes = encrypt(text.toByteArray())
            return encryptedBytes?.let 
                // NO_WRAP is important as was getting \\n at the end
                Base64.encodeToString(it, Base64.NO_WRAP)
            
        
        /**
         * 将 text 解密
         */
        fun decrypt(base64EncodedText: String): String? 
            val decodedCipherBytes = Base64.decode(base64EncodedText, Base64.NO_WRAP)
            val decryptedBytes = decrypt(decodedCipherBytes)
            return decryptedBytes?.let  String(it) 
        
    

(注意:上面代码使用了一些API要求在 Android 6.0 以上)

以下 Cipher 类支持设置的加密算法:

上面代码中 Cipher 使用的就是 AES/CBC/PKCS7Padding 这一种加密算法。

这里关键的一点是加密解密用的 key 是使用 KeyStore 来存储,它是 Android 的密钥库系统,使用 KeyStore 存储的密钥只能使用但是不能从设备中导出,安全性更好。详情可以参考:Android 密钥库系统

使用方式:

// 加密结果
val textEncrypted = CryptoManager.encrypt("hello world") ?: ""
// 解密结果
val textDecrypted = CryptoManager.decrypt(textEncrypted) ?: ""

这种使用方式是将文本加密后以Base64的字符串形式返回,解密时再传回这段Base64字符串。

还可以直接加密成 byte 数组来保存,但解密时同样需要传回 byte 数组:

// 加密结果
var encryptedBytes: ByteArray? = CryptoManager.encrypt(text.toByteArray())
// 如需将加密结果转换成文本,可以这样做
val textEncrypted = encryptedBytes?.let  String(it)  ?: ""

// 解密结果
var decryptedBytes: ByteArray? = encryptedBytes?.let  CryptoManager.decrypt(it) 
// 如需将解密结果转换成文本,可以这样做
val textDecrypted = decryptedBytes?.let  String(it)   ?: ""

示例 demo:

import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.fly.mycompose.application.ui.theme.MyComposeApplicationTheme

@RequiresApi(Build.VERSION_CODES.M)
class EncryptActivity : ComponentActivity() 
    var encryptedBytes: ByteArray? = null
    var decryptedBytes: ByteArray? = null
    
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState) 
        setContent 
            MyComposeApplicationTheme 
                var text by remember  mutableStateOf("") 
                var textEncrypted by remember  mutableStateOf("") 
                var textDecrypted by remember  mutableStateOf("") 
                Column(modifier = Modifier
                    .fillMaxSize()
                    .padding(32.dp)
                    .verticalScroll(rememberScrollState())
                ) 
                    TextField(
                        value = text,
                        onValueChange =  text = it ,
                        modifier = Modifier.fillMaxWidth(),
                        placeholder =  Text(text = "请输入加密内容") 
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Row 
                        Button(onClick =  
                            textEncrypted = CryptoManager.encrypt(text) ?: "" 
                            // encryptedBytes = CryptoManager.encrypt(text.toByteArray())
                            // textEncrypted = encryptedBytes?.let  String(it)  ?: ""
                        ) 
                            Text(text = "加密")
                        
                        Spacer(modifier = Modifier.width(16.dp))
                        Button(onClick =  
                            textDecrypted = CryptoManager.decrypt(textEncrypted) ?: "" 
                            // decryptedBytes = encryptedBytes?.let  CryptoManager.decrypt(it) 
                            // textDecrypted = decryptedBytes?.let  String(it)   ?: ""
                        ) 
                            Text(text = "解密")
                        
                    
                    Text(text = "加密后的内容:$textEncrypted")
                    Text(text = "解密后的内容:$textDecrypted")
                
            
        
    

下面是另一个工具类,在其他地方看到的,实现方式也是类似,这个没有API在 Android 6.0 以上的限制,但是它没有使用 KeyStore 来存储 key,在加密/解密时还需要显示的指定一个密码。

import android.util.Base64
import android.util.Base64.*
import java.io.UnsupportedEncodingException
import java.security.GeneralSecurityException
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

object AESCryptUtil 
    private const val AES_MODE = "AES/CBC/PKCS7Padding"
    private const val CHARSET = "UTF-8"
    private const val CIPHER = "AES"
    private const val HASH_ALGORITHM = "SHA-256"
    private val IV_BYTES = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)

    /**
     * Generates SHA256 hash of the password which is used as key
     *
     * @param password used to generated key
     * @return SHA256 of the password
     */
    private fun generateKey(password: String): SecretKeySpec 
        val digest = MessageDigest.getInstance(HASH_ALGORITHM)
        val bytes = password.toByteArray(charset(CHARSET))
        digest.update(bytes, 0, bytes.size)
        val key = digest.digest()

        return SecretKeySpec(key, CIPHER)
    

    /**
     * Encrypt and encode message using 256-bit AES with key generated from password.
     *
     * @param password used to generated key
     * @param message  the thing you want to encrypt assumed String UTF-8
     * @return Base64 encoded CipherText
     * @throws GeneralSecurityException if problems occur during encryption
     */
    fun encrypt(password: String, message: String): String 
        try 
            val key = generateKey(password)
            val cipherText = encrypt(key, IV_BYTES, message.toByteArray(charset(CHARSET)))
            //NO_WRAP is important as was getting \\n at the end
            return Base64.encodeToString(cipherText, Base64.NO_WRAP)
         catch (e: UnsupportedEncodingException) 
            throw GeneralSecurityException(e)
        

    

    /**
     * More flexible AES encrypt that doesn't encode
     *
     * @param key     AES key typically 128, 192 or 256 bit
     * @param iv      Initiation Vector
     * @param message in bytes (assumed it's already been decoded)
     * @return Encrypted cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    @Throws(GeneralSecurityException::class)
    fun encrypt(key: SecretKeySpec, iv: ByteArray, message: ByteArray): ByteArray 
        val cipher = Cipher.getInstance(AES_MODE)
        val ivSpec = IvParameterSpec(iv)
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
        return cipher.doFinal(message)
    

    /**
     * Decrypt and decode ciphertext using 256-bit AES with key generated from password
     *
     * @param password                used to generated key
     * @param base64EncodedCipherText the encrpyted message encoded with base64
     * @return message in Plain text (String UTF-8)
     * @throws GeneralSecurityException if there's an issue decrypting
     */
    @Throws(GeneralSecurityException::class)
    fun decrypt(password: String, base64EncodedCipherText: String): String 
        try 
            val key = generateKey(password)
            val decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP)
            val decryptedBytes = decrypt(key, IV_BYTES, decodedCipherText)
            return String(decryptedBytes, charset(CHARSET))
         catch (e: UnsupportedEncodingException) 
            throw GeneralSecurityException(e)
        

    

    /**
     * More flexible AES decrypt that doesn't encode
     *
     * @param key               AES key typically 128, 192 or 256 bit
     * @param iv                Initiation Vector
     * @param decodedCipherText in bytes (assumed it's already been decoded)
     * @return Decrypted message cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    @Throws(GeneralSecurityException::class)
    fun decrypt(key: SecretKeySpec, iv: ByteArray, decodedCipherText: ByteArray): ByteArray 
        val cipher = Cipher.getInstance(AES_MODE)
        val ivSpec = IvParameterSpec(iv)
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
        return cipher.doFinal(decodedCipherText)
    

使用方式:

// 加密结果
val textEncrypted = AESCryptUtil.encrypt("123456", "hello world")
// 解密结果
val textDecrypted = AESCryptUtil.decrypt("123456", textEncrypted)

以上是关于Android AES加密解密工具类的主要内容,如果未能解决你的问题,请参考以下文章

Android数据加密之Aes加密

我的Android进阶之旅------>Android采用AES+RSA的加密机制对http请求进行加密

Android加密篇 AES

Android 加密/解密问题 (AES)

AES加密 在PC上和Android上不一样 怎么解决

[Android Pro] AES加密