登录数据加密解密成功
This commit is contained in:
		| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Wed Jun 04 07:03:38 GMT 2025 | ||||
| #Thu Jun 05 01:50:42 GMT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=2 | ||||
| buildCount=38 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -11,6 +11,8 @@ import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import java.security.KeyPair; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import cc.winboll.studio.libappbase.utils.YunUtils; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
| @@ -48,24 +50,29 @@ public class LogonActivity extends Activity implements IWinBoLLActivity { | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     public void onTestLogin(View view) { | ||||
|         LogUtils.d(TAG, "onTestLogin"); | ||||
|         YunUtils yunUtils = YunUtils.getInstance(this); | ||||
|         yunUtils.login(); | ||||
|         yunUtils.checkLoginStatus(); | ||||
|     } | ||||
|  | ||||
|     public void onTestRSA(View view) { | ||||
|         LogUtils.d(TAG, "onTestRSA"); | ||||
|         String keyPath = getFilesDir() + "/home/keys/"; // 密钥保存路径 | ||||
|         LogUtils.d(TAG, String.format("keyPath %s", keyPath)); | ||||
|         RSAUtils utils = RSAUtils.getInstance(); | ||||
|  | ||||
|         RSAUtils utils = RSAUtils.getInstance(this); | ||||
|          | ||||
|         try { | ||||
|             // 测试 1:首次生成密钥对 | ||||
|             LogUtils.d(TAG, "==== 首次生成密钥对 ===="); | ||||
|             if (utils.keysExist(keyPath)) { | ||||
|             if (utils.keysExist()) { | ||||
|                 LogUtils.d(TAG, "密钥对已生成"); | ||||
|             } else { | ||||
|                 utils.generateAndSaveKeys(keyPath); | ||||
|                 LogUtils.d(TAG, "密钥对生成成功,保存至:" + keyPath); | ||||
|                 utils.generateAndSaveKeys(); | ||||
|                 LogUtils.d(TAG, "密钥对生成成功。"); | ||||
|             } | ||||
|  | ||||
|             // 测试 2:获取密钥对(自动读取已生成的文件) | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(keyPath); | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|             PublicKey publicKey = keyPair.getPublic(); | ||||
|             PrivateKey privateKey = keyPair.getPrivate(); | ||||
|  | ||||
| @@ -78,7 +85,7 @@ public class LogonActivity extends Activity implements IWinBoLLActivity { | ||||
|  | ||||
|             // 测试 3:重复调用时检查是否复用文件 | ||||
|             LogUtils.d(TAG, "\n==== 二次调用 ===="); | ||||
|             KeyPair reusedPair = utils.getOrGenerateKeys(keyPath); | ||||
|             KeyPair reusedPair = utils.getOrGenerateKeys(); | ||||
|             LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用) | ||||
|             LogUtils.d(TAG, "操作完成"); | ||||
|  | ||||
| @@ -94,8 +101,8 @@ public class LogonActivity extends Activity implements IWinBoLLActivity { | ||||
|  | ||||
|             // 3. 私钥解密 | ||||
|             String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused); | ||||
|             LogUtils.d(TAG, "解密结果:" + decryptedMessage); | ||||
|  | ||||
|             LogUtils.d(TAG, "解密结果: " + decryptedMessage); | ||||
|              | ||||
|             // 4. 验证解密是否成功 | ||||
|             if (testMessage.equals(decryptedMessage)) { | ||||
|                 LogUtils.d(TAG, "加密解密测试通过!"); | ||||
|   | ||||
| @@ -17,8 +17,6 @@ public class APPModel extends BaseBean { | ||||
|     // 应用是否处于正在调试状态 | ||||
|     // | ||||
|     boolean isDebuging = false; | ||||
|     // 用本机 RSA 加密后保存的令牌 | ||||
|     String rsaToken = ""; | ||||
|  | ||||
|     public APPModel() { | ||||
|         this.isDebuging = false; | ||||
| @@ -28,14 +26,6 @@ public class APPModel extends BaseBean { | ||||
|         this.isDebuging = isDebuging; | ||||
|     } | ||||
|  | ||||
|     public void setRsaToken(String rsaToken) { | ||||
|         this.rsaToken = rsaToken; | ||||
|     } | ||||
|  | ||||
|     public String getRsaToken() { | ||||
|         return rsaToken; | ||||
|     } | ||||
|  | ||||
|     public void setIsDebuging(boolean isDebuging) { | ||||
|         this.isDebuging = isDebuging; | ||||
|     } | ||||
| @@ -53,7 +43,6 @@ public class APPModel extends BaseBean { | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isDebuging").value(isDebuging()); | ||||
|         jsonWriter.name("rsaToken").value(getRsaToken()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -61,8 +50,6 @@ public class APPModel extends BaseBean { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isDebuging")) { | ||||
|                 setIsDebuging(jsonReader.nextBoolean()); | ||||
|             } else if (name.equals("rsaToken")) { | ||||
|                 setRsaToken(jsonReader.nextString()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|   | ||||
| @@ -0,0 +1,92 @@ | ||||
| package cc.winboll.studio.libappbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 19:14 | ||||
|  */ | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import android.util.JsonWriter; | ||||
| import android.util.JsonReader; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class UserInfoModel extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "UserInfoModel"; | ||||
|  | ||||
|     String username; | ||||
|     String password; | ||||
|     String token; | ||||
|  | ||||
|     public UserInfoModel() { | ||||
|         this.username = ""; | ||||
|         this.password = ""; | ||||
|         this.token = ""; | ||||
|     } | ||||
|  | ||||
|     public void setUsername(String username) { | ||||
|         this.username = username; | ||||
|     } | ||||
|  | ||||
|     public String getUsername() { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     public void setPassword(String password) { | ||||
|         this.password = password; | ||||
|     } | ||||
|  | ||||
|     public String getPassword() { | ||||
|         return password; | ||||
|     } | ||||
|  | ||||
|     public void setToken(String token) { | ||||
|         this.token = token; | ||||
|     } | ||||
|  | ||||
|     public String getToken() { | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return UserInfoModel.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("username").value(getUsername()); | ||||
|         jsonWriter.name("password").value(getPassword()); | ||||
|         jsonWriter.name("token").value(getToken()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("username")) { | ||||
|                 setUsername(jsonReader.nextString()); | ||||
|             } else if (name.equals("password")) { | ||||
|                 setPassword(jsonReader.nextString()); | ||||
|             } else if (name.equals("token")) { | ||||
|                 setToken(jsonReader.nextString()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,128 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 20:15 | ||||
|  * @Describe 文件操作类 | ||||
|  */ | ||||
| import java.io.BufferedReader; | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.FileReader; | ||||
| import java.io.FileWriter; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class FileUtils { | ||||
|     public static final String TAG = "FileUtils"; | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 语法) | ||||
|      */ | ||||
|     public static byte[] readFileToByteArray(String filePath) { | ||||
|         FileInputStream fis = null; | ||||
|         ByteArrayOutputStream bos = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(filePath); | ||||
|             bos = new ByteArrayOutputStream(); | ||||
|             byte[] buffer = new byte[4096]; | ||||
|             int bytesRead; | ||||
|             while ((bytesRead = fis.read(buffer)) != -1) { | ||||
|                 bos.write(buffer, 0, bytesRead); | ||||
|             } | ||||
|             return bos.toByteArray(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             // 手动关闭流(Java 7 不支持 try-with-resources) | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|             if (bos != null) { | ||||
|                 try { | ||||
|                     bos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 写入字节数组到文件(Java 7 语法) | ||||
|      */ | ||||
|     public static boolean writeByteArrayToFile(byte[] data, String filePath) { | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(filePath); | ||||
|             fos.write(data); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 原字符串读写方法(适配 Java 7) | ||||
|     public static String readFileToString(String filePath) { | ||||
|         BufferedReader reader = null; | ||||
|         try { | ||||
|             reader = new BufferedReader(new FileReader(filePath)); | ||||
|             StringBuilder content = new StringBuilder(); | ||||
|             String line; | ||||
|             while ((line = reader.readLine()) != null) { | ||||
|                 content.append(line).append(System.getProperty("line.separator")); | ||||
|             } | ||||
|             // 去除最后一个换行符(可选) | ||||
|             if (content.length() > 0) { | ||||
|                 content.deleteCharAt(content.length() - 1); | ||||
|             } | ||||
|             return content.toString(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             if (reader != null) { | ||||
|                 try { | ||||
|                     reader.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static boolean writeStringToFile(String content, String filePath, boolean append) { | ||||
|         BufferedWriter writer = null; | ||||
|         try { | ||||
|             writer = new BufferedWriter(new FileWriter(filePath, append)); | ||||
|             writer.write(content); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (writer != null) { | ||||
|                 try { | ||||
|                     writer.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -5,117 +5,193 @@ package cc.winboll.studio.libappbase.utils; | ||||
|  * @Date 2025/06/04 13:36 | ||||
|  * @Describe RSA加密工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Paths; | ||||
| import java.security.KeyFactory; | ||||
| import java.security.KeyPair; | ||||
| import java.security.KeyPairGenerator; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.security.spec.InvalidKeySpecException; | ||||
| import java.security.spec.PKCS8EncodedKeySpec; | ||||
| import java.security.spec.X509EncodedKeySpec; | ||||
| import java.util.Objects; | ||||
| import javax.crypto.Cipher; | ||||
|  | ||||
| public class RSAUtils { | ||||
|     public static final String TAG = "RSAUtils"; | ||||
|     private static final RSAUtils INSTANCE = new RSAUtils(); | ||||
|     private static final String TAG = "RSAUtils"; | ||||
|     private static final int KEY_SIZE = 2048; | ||||
|     private static final String KEY_ALGORITHM = "RSA"; | ||||
|     private static final String PUBLIC_KEY_FILE = "public.key"; | ||||
|     private static final String PRIVATE_KEY_FILE = "private.key"; | ||||
|     private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式 | ||||
|  | ||||
|     private RSAUtils() {} | ||||
|     private final String keyPath; | ||||
|     private static volatile RSAUtils INSTANCE; | ||||
|  | ||||
|     public static RSAUtils getInstance() { | ||||
|     /** | ||||
|      * 构造方法:初始化密钥存储路径(内部存储) | ||||
|      */ | ||||
|     private RSAUtils(Context context) { | ||||
|         keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取单例实例 | ||||
|      */ | ||||
|     public static synchronized RSAUtils getInstance(Context context) { | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new RSAUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查密钥文件是否存在 | ||||
|      */ | ||||
|     public boolean keysExist(String path) { | ||||
|         File publicKeyFile = new File(path + PUBLIC_KEY_FILE); | ||||
|         File privateKeyFile = new File(path + PRIVATE_KEY_FILE); | ||||
|     public boolean keysExist() { | ||||
|         File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE); | ||||
|         File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE); | ||||
|         return publicKeyFile.exists() && privateKeyFile.exists(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成密钥对并保存为字节文件 | ||||
|      * 生成密钥对并保存到文件 | ||||
|      */ | ||||
|     public void generateAndSaveKeys(String path) throws Exception { | ||||
|         LogUtils.d(TAG, "正在生成密钥对"); | ||||
|         // 生成密钥对 | ||||
|     public void generateAndSaveKeys() throws Exception { | ||||
|         LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)"); | ||||
|         KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); | ||||
|         generator.initialize(KEY_SIZE); | ||||
|         KeyPair keyPair = generator.generateKeyPair(); | ||||
|  | ||||
|         // 保存公钥(X.509字节) | ||||
|         saveKey(path, PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded()); | ||||
|         // 保存私钥(PKCS#8字节) | ||||
|         saveKey(path, PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); | ||||
|         saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded()); | ||||
|         saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); | ||||
|         LogUtils.d(TAG, "密钥对生成并保存成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取密钥对(存在则读取,不存在则生成) | ||||
|      * 获取或生成密钥对(线程安全) | ||||
|      */ | ||||
|     public KeyPair getOrGenerateKeys(String path) throws Exception { | ||||
|         if (keysExist(path)) { | ||||
|             LogUtils.d(TAG, "密钥对已存在"); | ||||
|             return readKeysFromFile(path); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "未生成密钥对"); | ||||
|             generateAndSaveKeys(path); | ||||
|             return readKeysFromFile(path); | ||||
|     public KeyPair getOrGenerateKeys() throws Exception { | ||||
|         if (!keysExist()) { | ||||
|             synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成 | ||||
|                 if (!keysExist()) { | ||||
|                     generateAndSaveKeys(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return readKeysFromFile(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从文件读取密钥对 | ||||
|      */ | ||||
|     private KeyPair readKeysFromFile(String path) throws Exception { | ||||
|         LogUtils.d(TAG, "正在读取密钥对"); | ||||
|         byte[] publicKeyBytes = Files.readAllBytes(Paths.get(path + PUBLIC_KEY_FILE)); | ||||
|         byte[] privateKeyBytes = Files.readAllBytes(Paths.get(path + PRIVATE_KEY_FILE)); | ||||
|     private KeyPair readKeysFromFile() throws Exception { | ||||
|         LogUtils.d(TAG, "读取密钥对文件"); | ||||
|         try { | ||||
|             byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE); | ||||
|             byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE); | ||||
|  | ||||
|         X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); | ||||
|         PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes); | ||||
|             X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); | ||||
|             PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes); | ||||
|  | ||||
|         KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); | ||||
|         PublicKey publicKey = factory.generatePublic(publicSpec); | ||||
|         PrivateKey privateKey = factory.generatePrivate(privateSpec); | ||||
|             KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); | ||||
|             PublicKey publicKey = factory.generatePublic(publicSpec); | ||||
|             PrivateKey privateKey = factory.generatePrivate(privateSpec); | ||||
|  | ||||
|         return new KeyPair(publicKey, privateKey); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 通用保存密钥字节方法 | ||||
|      */ | ||||
|     private void saveKey(String path, String fileName, byte[] keyBytes) throws IOException { | ||||
|         File dir = new File(path); | ||||
|         if (!dir.exists()) dir.mkdirs(); // 自动创建目录 | ||||
|         try (FileOutputStream fos = new FileOutputStream(path + fileName)) { | ||||
|             fos.write(keyBytes); | ||||
|             return new KeyPair(publicKey, privateKey); | ||||
|         } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { | ||||
|             LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage()); | ||||
|             throw new Exception("密钥文件损坏或格式错误", e); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * 公钥加密 | ||||
|      * 保存密钥到文件(通用方法) | ||||
|      */ | ||||
|     private void saveKey(String fileName, byte[] keyBytes) throws IOException { | ||||
|         Objects.requireNonNull(keyBytes, "密钥字节数据不可为空"); | ||||
|         File dir = new File(keyPath); | ||||
|         if (!dir.exists() && !dir.mkdirs()) { | ||||
|             throw new IOException("创建密钥目录失败:" + keyPath); | ||||
|         } | ||||
|  | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(keyPath + fileName); | ||||
|             fos.write(keyBytes); | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 兼容) | ||||
|      */ | ||||
|     private byte[] readFileToBytes(String filePath) throws IOException { | ||||
|         File file = new File(filePath); | ||||
|         if (!file.exists() || file.isDirectory()) { | ||||
|             throw new IOException("文件不存在或为目录:" + filePath); | ||||
|         } | ||||
|  | ||||
|         FileInputStream fis = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(file); | ||||
|             byte[] data = new byte[(int) file.length()]; | ||||
|             int bytesRead = fis.read(data); | ||||
|             if (bytesRead != data.length) { | ||||
|                 throw new IOException("文件读取不完整"); | ||||
|             } | ||||
|             return data; | ||||
|         } finally { | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 公钥加密(带参数校验) | ||||
|      */ | ||||
|     public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception { | ||||
|         Cipher cipher = Cipher.getInstance(KEY_ALGORITHM + "/ECB/PKCS1Padding"); | ||||
|         Objects.requireNonNull(plainText, "明文不可为空"); | ||||
|         Objects.requireNonNull(publicKey, "公钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.ENCRYPT_MODE, publicKey); | ||||
|  | ||||
|         // 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding) | ||||
|         int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度 | ||||
|         if (plainText.getBytes("UTF-8").length > maxPlainTextSize) { | ||||
|             throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节"); | ||||
|         } | ||||
|  | ||||
|         return cipher.doFinal(plainText.getBytes("UTF-8")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 私钥解密 | ||||
|      * 私钥解密(带参数校验) | ||||
|      */ | ||||
|     public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception { | ||||
|         Cipher cipher = Cipher.getInstance(KEY_ALGORITHM + "/ECB/PKCS1Padding"); | ||||
|         Objects.requireNonNull(encryptedData, "密文不可为空"); | ||||
|         Objects.requireNonNull(privateKey, "私钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.DECRYPT_MODE, privateKey); | ||||
|         byte[] decryptedBytes = cipher.doFinal(encryptedData); | ||||
|         return new String(decryptedBytes, "UTF-8"); | ||||
|   | ||||
| @@ -0,0 +1,139 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 17:21 | ||||
|  * @Describe 应用登录与接口工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.models.APPModel; | ||||
| import cc.winboll.studio.libappbase.models.UserInfoModel; | ||||
| import java.io.File; | ||||
| import java.security.KeyPair; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| public class YunUtils { | ||||
|     public static final String TAG = "YunUtils"; | ||||
|     // 私有静态实例,类加载时创建 | ||||
|     private static volatile YunUtils INSTANCE; | ||||
|     Context mContext; | ||||
|     UserInfoModel mUserInfoModel; | ||||
|     String token = ""; | ||||
|     String mDataFolderPath = ""; | ||||
|     String mUserInfoModelPath = ""; | ||||
|  | ||||
|     // 私有构造方法,防止外部实例化 | ||||
|     private YunUtils(Context context) { | ||||
|         LogUtils.d(TAG, "YunUtils"); | ||||
|         mContext = context; | ||||
|         mDataFolderPath = mContext.getExternalFilesDir(TAG).toString(); | ||||
|         File fTest = new File(mDataFolderPath); | ||||
|         if (!fTest.exists()) { | ||||
|             fTest.mkdirs(); | ||||
|         } | ||||
|         mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson"; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     // 公共静态方法,返回唯一实例 | ||||
|     public static synchronized YunUtils getInstance(Context context) { | ||||
|         LogUtils.d(TAG, "getInstance"); | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new YunUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     public void login() { | ||||
|         String token = getHostToken(null); | ||||
|         LogUtils.d(TAG, String.format("login token is %s", token)); | ||||
|         saveLocalToken(token); | ||||
|     } | ||||
|  | ||||
|     public void logout() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void checkLoginStatus() { | ||||
|         String token = getLocalToken(); | ||||
|         LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token)); | ||||
|     } | ||||
|  | ||||
|     String getLocalToken() { | ||||
|         UserInfoModel userInfoModel = loadUserInfoModel(); | ||||
|         return (userInfoModel == null) ?"": userInfoModel.getToken(); | ||||
|     } | ||||
|  | ||||
|     String getHostToken(UserInfoModel userInfoModel) { | ||||
|         String token = "uuyyhh"; | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     void saveLocalToken(String token) { | ||||
|         UserInfoModel userInfoModel = new UserInfoModel(); | ||||
|         userInfoModel.setToken(token); | ||||
|         saveUserInfoModel(userInfoModel); | ||||
|     } | ||||
|  | ||||
|     UserInfoModel loadUserInfoModel() { | ||||
|         LogUtils.d(TAG, "loadUserInfoModel"); | ||||
|         if (new File(mUserInfoModelPath).exists()) { | ||||
|             try { | ||||
|                 // 加载加密后的模型数据 | ||||
|                 byte[] encryptedData = FileUtils.readFileToByteArray(mUserInfoModelPath); | ||||
|                 // 加载 RSA 工具 | ||||
|                 RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|                 KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|                 //PublicKey publicKey = keyPair.getPublic(); | ||||
|                 PrivateKey privateKey = keyPair.getPrivate(); | ||||
|                 // 私钥解密模型数据 | ||||
|                 String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|                 LogUtils.d(TAG, String.format("szInfo %s", szInfo)); | ||||
|                 mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class); | ||||
|                 if (mUserInfoModel == null) { | ||||
|                     LogUtils.d(TAG, "模型数据解析为空数据。"); | ||||
|                 } | ||||
|                 LogUtils.d(TAG, "UserInfoModel 解密加载结束。"); | ||||
|             } catch (Exception e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "云服务登录信息不存在。"); | ||||
|             mUserInfoModel = null; | ||||
|         } | ||||
|         return mUserInfoModel; | ||||
|     } | ||||
|  | ||||
|     void saveUserInfoModel(UserInfoModel userInfoModel) { | ||||
|         LogUtils.d(TAG, "saveUserInfoModel"); | ||||
|         try { | ||||
|             String szInfo = userInfoModel.toString(); | ||||
|             LogUtils.d(TAG, "原始数据: " + szInfo); | ||||
|  | ||||
|             RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|             PublicKey publicKey = keyPair.getPublic(); | ||||
|  | ||||
|             // 公钥加密(传入字节数组,避免中间字符串转换) | ||||
|             byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey); | ||||
|  | ||||
|             // 保存加密字节数组到文件(直接操作字节,无需转字符串) | ||||
|             FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath); | ||||
|             LogUtils.d(TAG, "加密数据已保存"); | ||||
|  | ||||
|             // 测试解密(仅调试用) | ||||
|             String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|             LogUtils.d(TAG, "解密结果: " + szInfo2); | ||||
|  | ||||
|             mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class); | ||||
|             if (mUserInfoModel == null) { | ||||
|                 LogUtils.d(TAG, "模型解析失败"); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, "加密/解密失败: " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -18,6 +18,12 @@ | ||||
| 			android:text="Test RSA" | ||||
|             android:onClick="onTestRSA"/> | ||||
|  | ||||
|         <Button | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="Test Login" | ||||
|             android:onClick="onTestLogin"/> | ||||
|          | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen