詳解Android端與JavaWeb傳輸加密(DES+RSA)
一、加密介紹
本文采用對稱式加密算法DES和非對稱式加密算法RSA結合做數(shù)據(jù)傳輸加密的方式。
先說一下對稱式加密 DES:對稱式加密即使用單鑰密碼加密的方法,信息的加密和解密使用同一個秘鑰,這種方式也稱為單秘鑰加密。所謂對稱就是指加密和解密使用的是同一個秘鑰!
常用的對稱加密有:DES、IDEA、RC2、RC4、SKIPJACK、RC5、AES算法等。
與對稱加密算法不同,非對稱加密算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰 (privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數(shù)據(jù)進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數(shù)據(jù)進行加密,那么只有用對應的公開密鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
RSA 公鑰加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美國麻省理工學院)開發(fā)的。RSA取名來自開發(fā)他們?nèi)叩拿?。RSA是目前最有影響力的公鑰加密算法,它能夠抵抗到目前為止已知的所有密碼攻擊,已被ISO推薦為公鑰數(shù)據(jù)加密標準。 RSA算法基于一個十分簡單的數(shù)論事實:將兩個大素數(shù)相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。
二、RSA密鑰生成
RSA密鑰采用OpenSSL協(xié)議進行生成,本文僅簡單生成公鑰和私鑰,如有其它需要可以通過CA證書進行密鑰的生成
1、OpenSSL安裝
http://slproweb.com/products/Win32OpenSSL.html
請自行選擇32位64位進行下載安裝
2、打開工作空間
打開OpenSSL安裝目錄下的bin,運行OpenSSL.exe進入OpenSSL工作空間
3、密鑰生成
①、私鑰生成(生成位置位于bin目錄下)
genrsa -out rsa_private_key.pem 1024
openssl隨機生成了一份私鑰,加密長度是1024位。加密長度是指理論上最大允許”被加密的信息“長度的限制,也就是明文的長度限制。隨著這個參數(shù)的增大(比方說2048),允許的明文長度也會增加,但同時也會造成計算復雜度的極速增長。一般推薦的長度就是1024位(128字節(jié))
JAVA需要使用的私鑰需要經(jīng)過PKCS#8編碼,PHP程序不需要
當前私鑰格式需要轉換為pkcs#8的格式,命令為:
pkcs8 -topk8 -inform PEM -in pkcs8_rsa_private_key.pem -outform PEM -nocrypt
②、公鑰生成
rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
至此,RSA+DES相關前期準備工作完成
三、Android端配置
本文主要針對數(shù)據(jù)傳輸過程進行加密,采取加密Json字符串完成整個加密過程,由此,需要統(tǒng)一傳輸參數(shù)為"data=********&sign="*******************"的格式,如有其它需求請自行更改。
由于本項目網(wǎng)絡框架采用Retrofit+OkHttp的實現(xiàn)方式,所以對參數(shù)進行加密的過程由OkHttp攔截器來實現(xiàn)
public class EncryptionInterceptor implements Interceptor {
private Context mContext;
public EncryptionInterceptor(Context context) {
this.mContext = context;
}
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
RequestBody oldBody = request.body();
Buffer buffer = new Buffer();
if (oldBody != null) {
oldBody.writeTo(buffer);
}
String strOldBody = buffer.readUtf8();
Map<String, String> map = new HashMap<>();
String dataByte = URLDecoder.decode(strOldBody.substring(5), "utf-8");
try {
//獲取DES的key
byte[] desKey = DESCoder.initKey();
//DES加密數(shù)據(jù)
byte[] encrypt = DESCoder.encrypt(dataByte.getBytes(), desKey);
map.put("data", parseByte2HexStr(encrypt));
//RSA加密
RSAEncrypt rsaEncrypt = new RSAEncrypt();
InputStream inputStream = mContext.getResources().getAssets().open("rsa_public_key.pem");
//rsa設置公鑰
rsaEncrypt.loadPublicKey(inputStream);
//rsa加密DES的key
byte[] rsaData = rsaEncrypt.encrypt(rsaEncrypt.getPublicKey(), desKey);
map.put("sign", parseByte2HexStr(rsaData));
} catch (Exception e) {
e.printStackTrace();
}
FormBody body = new FormBody.Builder().add("data", map.get("data")).add("sign", map.get("sign")).build();
request = request.newBuilder().header("Content-Type", body.contentType().type()).header("Content-Length", String.valueOf(body.contentLength())).method(request.method(), body).build();
return chain.proceed(request);
}
/**
* 將二進制轉換成16進制
*
* @since v1.0
*/
private static String parseByte2HexStr(byte buf[]) {
StringBuilder sb = new StringBuilder();
for (byte aBuf : buf) {
String hex = Integer.toHexString(aBuf & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 按照key排序得到參數(shù)列表字符串
*
* @param paramValues 參數(shù)map對象
* @return 參數(shù)列表字符串
*/
public static String getParamsOrderByKey(Map<String, String> paramValues) {
String params = "";
List<String> paramNames = new ArrayList<>(paramValues.size());
paramNames.addAll(paramValues.keySet());
Collections.sort(paramNames);
for (String paramName : paramNames) {
if (params.equals("")) {
params += paramName + "=" + paramValues.get(paramName);
} else {
params += "&" + paramName + "=" + paramValues.get(paramName);
}
}
return params;
}
/**
* 將16進制轉換為二進制
*
* @since v1.0
*/
public byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
添加OkHttp攔截器
new OkHttpClient.Builder()
.addInterceptor(new EncryptionInterceptor(this))
.build();
RSA工具類實現(xiàn)
public class RSAEncrypt {
public static final String DEFAULT_PUBLIC_KEY =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1Qcf1zVOuhseFxvo6+FnVvEPs" + "\r" + "Uvczg6oX+HjMNksiiDWcNkbPHfznaPDtgoBY2xF0R8HGHbrT53LNvkj7UMcI48tq" + "\r" + "K+B4YdJHe9SgJVDCCiceLLGtf/ev206qJ/XgKgrLFD+vMmjIB8gQCkZvy/dxhEf1" + "\r" + "aAmoz5tdJhOVdxT7QwIDAQAB" + "\r";
public static final String DEFAULT_PRIVATE_KEY =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALVBx/XNU66Gx4XG" + "\r" +
"+jr4WdW8Q+xS9zODqhf4eMw2SyKINZw2Rs8d/Odo8O2CgFjbEXRHwcYdutPncs2+" + "\r" +
"SPtQxwjjy2or4Hhh0kd71KAlUMIKJx4ssa1/96/bTqon9eAqCssUP68yaMgHyBAK" + "\r" +
"Rm/L93GER/VoCajPm10mE5V3FPtDAgMBAAECgYAf1hEAHHNhSS0MUzmqV+q3ftzT" + "\r" +
"SnM+6hZbJXpaLAMgapo3+NSRFmxQXP9MSEqw0LGNIfloCdrB03o3pv98nOCIZCh7" + "\r" +
"PHsU2GhxJ04Qro+wKhK358326KNXCjjqVIBG0xMbJxVhjM2/jjfocxFpe5iD7h53" + "\r" +
"c+GvDgUVduAYO4I1GQJBAO21n2aIzQV3mScS1O8BRV+9CmHaDbVHqBetRoB3kJ2U" + "\r" +
"piflKTNofwWmTA5A8sKt8WcOz7LsB2SWcp9jNvatxA8CQQDDNCmfo6eix9e5f11K" + "\r" +
"Rf8sRiN7XGDzlKkZlmQAN0UtXdTP4AN9cuZrwnntWKysXr/zLntYLGYn9rdrohbD" + "\r" +
"9RGNAkBOEsog7iuQcSCfQcMoIN29PSSs0OaRtNBTvniadyrLZuhP0CeBGAAoRd9T" + "\r" +
"CyfwoxrXg3jaRkWDVxqcmQSTbq0nAkB8flcRhilSqsuNdYpE5VFxpiXY9jirAKO8" + "\r" +
"Our6LEXFQjOIhCEVr+L+1OA4HDa8FA2thXaK7H4WfMXMMmr8fN69AkEAuR0YU9My" + "\r" +
"snzWLDWYR5sNp90PhyDSL/HTZHBnebD+JlAYwoYRFYt+tXw0/PEmV2B3thYGPeiZ" + "\r" +
"kHKd/TeLIVbxGg==" + "\r";
/**
* 私鑰
*/
private RSAPrivateKey privateKey;
/**
* 公鑰
*/
private RSAPublicKey publicKey;
/**
* 字節(jié)數(shù)據(jù)轉字符串專用集合
*/
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* 獲取私鑰
*
* @return 當前的私鑰對象
*/
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
/**
* 獲取公鑰
*
* @return 當前的公鑰對象
*/
public RSAPublicKey getPublicKey() {
return publicKey;
}
/**
* 隨機生成密鑰對
*/
public void genKeyPair() {
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
this.privateKey = (RSAPrivateKey) keyPair.getPrivate();
this.publicKey = (RSAPublicKey) keyPair.getPublic();
}
/**
* 從文件中輸入流中加載公鑰
*
* @param in 公鑰輸入流
* @throws Exception 加載公鑰時產(chǎn)生的異常
*/
public void loadPublicKey(InputStream in) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
loadPublicKey(sb.toString());
} catch (IOException e) {
throw new Exception("公鑰數(shù)據(jù)流讀取錯誤");
} catch (NullPointerException e) {
throw new Exception("公鑰輸入流為空");
}
}
/**
* 從字符串中加載公鑰
*
* @param publicKeyStr 公鑰數(shù)據(jù)字符串
* @throws Exception 加載公鑰時產(chǎn)生的異常
*/
public void loadPublicKey(String publicKeyStr) throws Exception {
try {
// byte[] buffer = Base64.decode(publicKeyStr.getBytes(), Base64.DEFAULT);
BASE64Decoder base64Decoder= new BASE64Decoder();
byte[] buffer= base64Decoder.decodeBuffer(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
this.publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("無此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公鑰非法");
} catch (NullPointerException e) {
throw new Exception("公鑰數(shù)據(jù)為空");
}
}
/**
* 從文件中加載私鑰
*
* @return 是否成功
* @throws Exception
*/
public void loadPrivateKey(InputStream in) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String readLine = null;
StringBuilder sb = new StringBuilder();
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
loadPrivateKey(sb.toString());
} catch (IOException e) {
throw new Exception("私鑰數(shù)據(jù)讀取錯誤");
} catch (NullPointerException e) {
throw new Exception("私鑰輸入流為空");
}
}
public void loadPrivateKey(String privateKeyStr) throws Exception {
try {
//byte[] buffer = Base64.encode(privateKeyStr.getBytes(), Base64.DEFAULT);
BASE64Decoder base64Decoder= new BASE64Decoder();
byte[] buffer= base64Decoder.decodeBuffer(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
this.privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("無此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私鑰非法");
} catch (NullPointerException e) {
throw new Exception("私鑰數(shù)據(jù)為空");
}
}
/**
* 加密過程
*
* @param publicKey 公鑰
* @param plainTextData 明文數(shù)據(jù)
* @return
* @throws Exception 加密過程中的異常信息
*/
public byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception {
if (publicKey == null) {
throw new Exception("加密公鑰為空, 請設置");
}
Cipher cipher = null;
try {
cipher = Cipher.getInstance("RSA");
//Android端無需添加此加密提供者,已默認實現(xiàn)
//cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plainTextData);
} catch (NoSuchAlgorithmException e) {
throw new Exception("無此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("加密公鑰非法,請檢查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文長度非法");
} catch (BadPaddingException e) {
throw new Exception("明文數(shù)據(jù)已損壞");
}
}
/**
* 解密過程
*
* @param privateKey 私鑰
* @param cipherData 密文數(shù)據(jù)
* @return 明文
* @throws Exception 解密過程中的異常信息
*/
public byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception {
if (privateKey == null) {
throw new Exception("解密私鑰為空, 請設置");
}
Cipher cipher = null;
try {
cipher = Cipher.getInstance("RSA");
//Android端無需添加此加密提供者,已默認實現(xiàn)
//cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return output;
} catch (NoSuchAlgorithmException e) {
throw new Exception("無此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("解密私鑰非法,請檢查");
} catch (IllegalBlockSizeException e) {
throw new Exception("密文長度非法");
} catch (BadPaddingException e) {
throw new Exception("密文數(shù)據(jù)已損壞");
}
}
/**
* 字節(jié)數(shù)據(jù)轉十六進制字符串
*
* @param data 輸入數(shù)據(jù)
* @return 十六進制內(nèi)容
*/
public static String byteArrayToString(byte[] data) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < data.length; i++) {
//取出字節(jié)的高四位 作為索引得到相應的十六進制標識符 注意無符號右移
stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);
//取出字節(jié)的低四位 作為索引得到相應的十六進制標識符
stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
if (i < data.length - 1) {
stringBuilder.append(' ');
}
}
return stringBuilder.toString();
}
public static void main(String[] args) {
RSAEncrypt rsaEncrypt = new RSAEncrypt();
//加載公鑰
try {
rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);
System.out.println("加載公鑰成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加載公鑰失敗");
}
/*try {
rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);
System.out.println("加載公鑰成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加載公鑰失敗");
}
//加載私鑰
try {
rsaEncrypt.loadPrivateKey(RSAEncrypt.DEFAULT_PRIVATE_KEY);
System.out.println("加載私鑰成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加載私鑰失敗");
}
try {
SecureRandom sr = new SecureRandom();
KeyGenerator kg = KeyGenerator.getInstance("DES");
kg.init(56, sr);
SecretKey generateKey = kg.generateKey();
String encodeHexString = Hex.toHexString(generateKey.getEncoded());
System.out.println(encodeHexString);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//測試字符串
String encryptStr= "Test String chaijunkun";
try {
//加密
long encryptStart = System.currentTimeMillis();
byte[] cipher = rsaEncrypt.encrypt(rsaEncrypt.getPublicKey(), encryptStr.getBytes());
long encryptEnd = System.currentTimeMillis();
System.out.println("密文長度:"+ cipher.length);
System.out.println(RSAEncrypt.byteArrayToString(cipher));
System.out.println("加密時間"+ (encryptEnd-encryptStart));
//解密
long decryptStart = System.currentTimeMillis();
byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);
long decryptEnd = System.currentTimeMillis();
System.out.println("明文長度:"+ plainText.length);
System.out.println(RSAEncrypt.byteArrayToString(plainText));
System.out.println("解密時間"+ (decryptEnd-decryptStart));
System.out.println(new String(plainText));
} catch (Exception e) {
System.err.println(e.getMessage());
}*/
}
}
DES工具類實現(xiàn)
public class DESCoder {
/**
* 密鑰算法
* java 7只支持56位密鑰
* Bouncy Castle 支持64位密鑰
*/
public static final String KEY_ALGORITHM = "DES";
/**
* 加密/解密算法 /工作模式/填充方式
*/
public static final String CIPHER_ALGORITHM = "DES/ECB/PKCS5Padding";
/**
* 轉換密鑰
* @param key 二進制密鑰
* @return key 密鑰
* @throws Exception
*/
private static Key toKey(byte[] key) throws Exception{
//實例化DES密鑰材料
DESKeySpec dks = new DESKeySpec(key);
//實例化密鑰工廠
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
//生產(chǎn)密鑰
SecretKey secretKey = keyFactory.generateSecret(dks);
return secretKey;
}
/**
* 解密
* @param data 待解密數(shù)據(jù)
* @param key 密鑰
* @return byte[] 解密數(shù)據(jù)
* @throws Exception
*/
public static byte[] decrypt(byte[] data,byte[] key) throws Exception{
//還原密鑰
Key k = toKey(key);
//實例化
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
//初始化,設置為解密模式
cipher.init(Cipher.DECRYPT_MODE, k);
//執(zhí)行操作
return cipher.doFinal(data);
}
/**
* 加密
* @param data 待加密數(shù)據(jù)
* @param key 密鑰
* @return byte[] 加密數(shù)據(jù)
* @throws Exception
*/
public static byte[] encrypt(byte[] data,byte[] key) throws Exception{
//還原密鑰
Key k = toKey(key);
//實例化
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,new BouncyCastleProvider());
//初始化,設置為加密模式
cipher.init(Cipher.ENCRYPT_MODE,k);
//執(zhí)行操作
return cipher.doFinal(data);
}
/**
* 生產(chǎn)密鑰
* java 7只支持56位 密鑰
* Bouncy Castle 支持64位密鑰
* @return byte[] 二進制密鑰
* @throws Exception
*/
public static byte[] initKey() throws Exception{
/*
* 實例化密鑰生成器
* 若要使用64位密鑰注意替換
* 講下述代碼中的
* KeyGenerator.getInstance(KEY_ALGORITHM);
* 替換為
* KeyGenerator.getInstance(KEY_ALGORITHM,"BC");
*/
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM,new BouncyCastleProvider());
kg.init(64);
//生成密鑰
SecretKey secretKey = kg.generateKey();
//獲得密鑰的二進制編碼形式
return secretKey.getEncoded();
}
}
四、JavaWeb端配置
Web后端只需要在Controller中添加以下代碼,接受服務端傳遞的data和sign,并完成接收的Json字符串轉換為實體類即可
/**
* 解密所需數(shù)據(jù)
*
* @param data 接受客戶端上傳的Json格式的數(shù)據(jù)
* @param sign 接受客戶端上傳的解密數(shù)據(jù)的key值
*/
public <T> T convertJson(String data, String sign,Class<T> clazz) {
System.out.println(data);
System.out.println(sign);
T tClass = null;
try {
//rsa加密
RSAEncrypt rsaEncrypt = new RSAEncrypt();
//加載rsa私鑰
InputStream in = new FileInputStream(new File("C:\\OpenSSL-Win64\\bin\\pkcs8_rsa_private_key.pem"));
rsaEncrypt.loadPrivateKey(in);
//獲取RSA加密的key的數(shù)據(jù),并把該16進制的sign轉成byte[],(客戶端采用將byte[]轉成16進制進行數(shù)據(jù)上傳)
byte[] keyBytes = parseHexStr2Byte(sign);
//通過RSA解密DES的key值
byte[] rsaKey = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), keyBytes);
//通過DES的key值解密需要的json數(shù)據(jù)
byte[] desData = DESCoder.decrypt(parseHexStr2Byte(data), rsaKey);
System.out.println(Arrays.toString(desData));
System.out.println(new String(desData));
tClass = JSON.parseObject(new String(desData), clazz);
} catch (Exception e) {
e.printStackTrace();
}
return tClass;
}
五、備注
后續(xù)會上傳相關Demo到Github
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android Drawerlayout側拉欄事件傳遞問題的解決方法
這篇文章主要為大家詳細介紹了Android Drawerlayout側拉欄事件傳遞問題的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android動畫效果之自定義ViewGroup添加布局動畫(五)
這篇文章主要介紹了Android動畫效果之自定義ViewGroup添加布局動畫,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-08-08
Android動態(tài)權限申請實現(xiàn)步驟分解
對于一些危險權限在AndroidManifest清單文件中申請之后,還需要得到用戶的許可并打開,才算是真正的開啟了這個權限。所以可以使用動態(tài)申請權限,對于某個功能,如果需要開啟某個權限,在用戶使用它之前,彈窗提示用戶是否要開啟這個權限2023-04-04

