JAVA实现RSA算法:可将密钥存放到文件中并取出

该程序展示了如何生成RSA密钥对,并将它们保存到文件中,之后能够从文件中读取密钥进行加密和解密操作。若要了解如何将密钥存储到数据库,可以参考指定链接。

        本程序能实现生成RSA密钥保存到文件中并取出,再进行加解密,如需将密钥保存到数据库中,请参考http://blog.164288.com/post/21.html

package init;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import org.bouncycastle.jce.provider.*;
import sun.misc.*;

public class Client1
{
    public static void main(String[] args) throws Exception{

        Client1.init();
        KeyPair kp=Client1.generateKey();
        PublicKey pu=kp.getPublic();
        PrivateKey pr=kp.getPrivate();
        String pu2=Client1.getKeyAsString(pu);
        String pr2=Client1.getKeyAsString(pr);
        
    //将密钥保存到文件中
        
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("client1PubKey1.key"));  
        osw.write(pu2,0,pu2.length());  
        osw.flush();
        osw.close();
        
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("client1PriKey1.key"));  
        osw2.write(pr2,0,pr2.length());  
        osw2.flush();
        osw2.close();
        
        System.out.println("Save Key Success!");
        
    //将密钥从文件中取出来
        
        //取得client1的公钥,私钥
        InputStreamReader isr = new InputStreamReader(new FileInputStream("client1PubKey1.key"));    
        String client1PubKey="";
        int ch=0;
        while((ch=isr.read())!=-1){
            client1PubKey=client1PubKey+(char)ch;
        }
        isr.close();
        
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream("client1PriKey1.key"));    
        String client1PriKey="";
        int ch2=0;
        while((ch2=isr2.read())!=-1){
            client1PriKey=client1PriKey+(char)ch2;
        }
        isr2.close();
        
        System.out.println("client1PriKey="+client1PriKey);
        System.out.println("client1PubKey="+client1PubKey);
                
        String pt="12345"; //明文
         //私钥加密,公钥解密
         String ct=Client1.encrypt(pt, pr);
        String pt2=Client1.decrypt(ct, pu);
        System.out.println("plaText="+pt);
        System.out.println("cipText="+ct);
        System.out.println("plaText2="+pt2);
            
         //公钥加密,私钥解密
        String ctt=Client1.encrypt(pt, pu);
        String ptt2=Client1.decrypt(ctt, pr);
        System.out.println("cipTextt="+ctt);
        System.out.println("plaTextt2="+ptt2);    
    }
    
    public static final String ALGORITHM="RSA";
    public static void init(){
        
        Security.addProvider(new BouncyCastleProvider());
    }
    public static KeyPair generateKey() throws NoSuchAlgorithmException{

        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
        keyGen.initialize(1024);
        KeyPair key = keyGen.generateKeyPair();
        return key;
    }
    public String subSecEncrypt(String pText,Key key) throws Exception{
        
        //分段加密,注意:分段加密的密文长度都是174字节
        int pTextLen=pText.length();
        int m=pTextLen/117;
        int n=pTextLen-m*117;
        String cText=""; //密文
        //对117整数倍的子串加密,拼接
        for(int i=0;i<m;i++){
            String pTextSub=pText.substring(i*117, (i+1)*117); //获取子串
            cText=cText+encrypt(pTextSub, key); //分段加密,拼接
        }
        //对剩下的子串加密,拼接
        String pTextSub2=pText.substring(pTextLen-n, pTextLen);
        cText=cText+encrypt(pTextSub2, key);
        return cText;
    }
    public static String subSecDecrypt(String cText,Key key) throws Exception{
        
        //分段解密,注意:分段加解密的密文长度都是174字节
        int cTextLen=cText.length();
        int m=cTextLen/174;
        //int n=cTextLen-m*174;
        String pText=""; //明文
        for(int i=0;i<m;i++){
            String cTextSub=cText.substring(i*174, (i+1)*174);
            pText=pText+decrypt(cTextSub, key);
        }
        return pText;
    }
    public static byte[] encrypt(byte[] text, Key key) throws Exception
    {
        byte[] cipherText=null;
        Cipher cipher=Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        cipherText=cipher.doFinal(text);
        return cipherText;
    }
    public static String encrypt(String text, Key key) throws Exception
    {
        String encryptedText;
        byte[] cipherText=encrypt(text.getBytes("UTF8"),key);
        encryptedText=encodeBASE64(cipherText);
        return encryptedText;
    }
    public static byte[] decrypt(byte[] text, Key key) throws Exception
    {
        byte[] decryptedText=null;
        Cipher cipher=Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        decryptedText=cipher.doFinal(text);
        return decryptedText;
    }
    public static String decrypt(String text, Key key) throws Exception
    {
        String result;
        byte[] dectyptedText=decrypt(decodeBASE64(text),key);
        result=new String(dectyptedText,"UTF8");
        return result;
    }
    //将String类型转换为PrivateKey类型
    public static PrivateKey getPrivateKeyFromString(String key) throws Exception
    {
        KeyFactory keyFactory=KeyFactory.getInstance(ALGORITHM);
        BASE64Decoder b64=new BASE64Decoder();
        EncodedKeySpec privateKeySpec=new PKCS8EncodedKeySpec(b64.decodeBuffer(key));
        PrivateKey privateKey=keyFactory.generatePrivate(privateKeySpec);
        return privateKey;
    }
    //将String类型转换为PublicKey类型
    public static PublicKey getPublicKeyFromString(String key) throws Exception
    {
        BASE64Decoder b64=new BASE64Decoder();
        KeyFactory keyFactory=KeyFactory.getInstance(ALGORITHM);
        EncodedKeySpec publicKeySpec=new X509EncodedKeySpec(b64.decodeBuffer(key));
        PublicKey publicKey=keyFactory.generatePublic(publicKeySpec);
        return publicKey;
    }
    //将Key类型转换为String类型
    public static String getKeyAsString(Key key)
    {
        byte[] keyBytes = key.getEncoded();
        BASE64Encoder b64 = new BASE64Encoder();
        return b64.encode(keyBytes);
    }
    public static String encodeBASE64(byte[] bytes)
    {
        BASE64Encoder b64=new BASE64Encoder();
        return b64.encode(bytes);
    }
    public static byte[] decodeBASE64(String text) throws IOException
    {
        BASE64Decoder b64=new BASE64Decoder();
        return b64.decodeBuffer(text);
    }
}
 
<think>我们正在讨论APK文件结构和签名机制,特别是关于签名是否存储在DEX文件中,以及修改DEX后APK是否仍能正常安装的问题。 根据引用[2]和引用[5]: - META-INF文件存放签名证书,包括CERT.SF(摘要加密文件)、CERT.RSA(签名证书文件存放公钥和加密算法描述)和MANIFEST.MF(文件摘要,包含APK中所有文件的摘要明文)。 - 签名机制:在安装APK时,系统会校验整个APK的签名,包括对每个文件的摘要进行校验。 现在,用户的问题有两个: 1. 签名信息是否存储在DEX文件中? 2. 修改DEX文件后重新打包,是否会影响签名验证和安装过程? 解答: 1. **签名信息是否存储在DEX文件中?** 根据引用[5],签名信息存储在META-INF目录下的文件中(CERT.SF, CERT.RSA, MANIFEST.MF),而不是DEX文件中。DEX文件(classes.dex)包含的是编译后的代码。因此,签名信息不在DEX文件中。 2. **修改DEX文件后重新打包,是否会影响签名验证和安装过程?** - 当我们修改DEX文件(例如,修改代码后重新编译生成新的classes.dex)重新打包成APK,但没有重新签名,那么安装时会发生什么? - 根据引用[2]:系统在安装时会计算APK中每个文件的摘要(包括修改后的DEX文件),与META-INF/MANIFEST.MF中记录的摘要(即原始文件的摘要)进行对比。如果发现不一致(因为DEX文件被修改了,其摘要就会改变),则签名验证失败,导致安装失败(出现签名冲突的错误)。 - 另外,在引用[1]中描述了安装过程:系统会校验APK的签名,检查APK结构,校验dex文件。如果dex文件被破坏(或修改),则无法通过校验,进而无法安装。 因此,修改DEX文件后,如果不重新签名,那么APK的签名验证会失败,无法安装。 但是,如果修改DEX文件后,我们重新对APK进行签名(使用原来的签名证书或者新的签名证书),那么会生成新的签名文件(META-INF目录下的文件)。这时: - 如果使用原来的签名证书重新签名,那么对于用户设备上已经安装的同一个应用(相同包名)来说,更新安装时,系统会检查新APK的签名是否与旧版本一致。如果一致,则允许更新;如果不一致,则会出现`INSTALL_FAILED_UPDATE_INCOMPATIBLE`错误(如用户之前的问题)。 - 如果使用新的签名证书重新签名,那么设备上若已安装旧版本(使用旧签名),则无法直接更新(因为签名不同),必须先卸载旧版本。 注意:重新签名后,APK的签名信息(META-INF下的文件)被更新,因此安装时系统会验证新的签名。但是,如果设备上没有旧版本,那么安装新签名的APK是没有问题的。 总结: - 签名信息存储在META-INF目录下,不在DEX文件中。 - 修改DEX文件后,如果不重新签名,安装时会因为签名验证失败而无法安装。 - 修改DEX文件后重新签名,可以安装,但要注意: - 如果是更新安装,必须使用与旧版本相同的签名证书,否则需要先卸载旧版本。 补充:重新签名的步骤 1. 解压APK(或者直接修改DEX文件替换)。 2. 使用签名工具(如jarsigner或apksigner)重新签名: ```bash jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.jks my_application.apk alias_name ``` 或使用apksigner(Android官方推荐): ```bash apksigner sign --ks my-release-key.jks --out output.apk input.apk ``` 3. 然后安装重新签名的APK。 注意:如果修改了APK中的任何文件(包括DEX、资源文件等),都必须重新签名,否则无法安装。 相关问题: 1. 如何生成和管理Android应用的签名密钥? 2. 在Android Studio中如何配置签名以解决安装冲突? 3. 其他常见Android安装错误(如`INSTALL_FAILED_INSUFFICIENT_STORAGE`)该如何处理? 4. APK签名机制中使用的加密算法有哪些? 5. 如何验证APK的签名信息? 根据以上分析,我们给出用户问题的答案。</think>### APK文件结构、签名机制与DEX修改分析 #### 1. **APK文件结构解析** APK文件本质是ZIP格式压缩包,解压后包含以下核心文件: - **DEX文件** (`classes.dex`):包含编译后的Java/Kotlin字节码,由Dalvik/ART虚拟机执行 - **META-INF目录**:签名验证核心区 - `MANIFEST.MF`:存储所有文件的SHA摘要(明文) - `CERT.SF`:对MANIFEST.MF的加密摘要 - `CERT.RSA`:包含公钥和证书信息 - **资源文件**:`res/`, `assets/`, `AndroidManifest.xml`等 - **原生库**:`lib/`目录下的CPU架构相关SO文件 > **签名信息位置**:签名**不存储在DEX文件**中,而是完全独立存放在`META-INF`目录下[^2][^5]。 #### 2. **签名验证机制** Android采用分层签名验证: 1. **文件完整性校验** 安装时系统计算APK内每个文件的SHA摘要,与`MANIFEST.MF`中的记录比对 $$ \text{SHA}_{\text{new}}(file_i) \overset{?}{=} \text{SHA}_{\text{MF}}(file_i) $$ 若不等则判定文件被篡改[^2] 2. **签名链验证** - 用`CERT.RSA`中的公钥解密`CERT.SF` - 验证解密结果与`MANIFEST.MF`的摘要匹配 - 确保证书链可信(开发证书或CA签发) #### 3. **修改DEX的影响实验** ```bash # 实验步骤 apktool d original.apk # 解包APK vim original/smali/.../modify.smali # 修改DEX对应的smali代码 apktool b original -o modified.apk # 重打包 adb install modified.apk # 尝试安装 ``` **结果分析**: 1. **未重新签名** - 安装失败,错误:`INSTALL_PARSE_FAILED_NO_CERTIFICATES` - 原因:修改DEX导致文件SHA变化,与`MANIFEST.MF`记录不匹配[^2][^5] 2. **重新签名后** - 可安装成功,但需注意: - 若设备存在旧版本:触发`INSTALL_FAILED_UPDATE_INCOMPATIBLE`(签名变更) - 全新安装:正常使用(但属于不同应用) #### 4. **签名与DEX的关系图解 ```mermaid graph TD A[修改DEX文件] --> B{是否重新签名?} B -->|否| C[安装失败<br>文件摘要不匹配] B -->|是| D[生成新签名] D --> E{设备有旧版本?} E -->|是| F[需先卸载旧版] E -->|否| G[安装成功] ``` #### 5. **关键技术结论** 1. DEX文件**不存储签名信息**,签名独立存在于`META-INF` 2. 修改DEX会改变文件摘要,导致与原始签名记录不匹配 3. 重签名可解决验证问题,但会改变应用身份(新证书=新应用) 4. 系统级应用签名存储在`/system/etc/security/otacerts.zip` > 企业级解决方案:使用**V2/V3签名方案**(APK Signing Scheme)可保护ZIP元数据不被篡改,但DEX修改仍需重签名[^4]。 #### 相关问题 1. 如何提取和验证APK的签名证书信息? 2. Android V2/V3签名方案相比V1有哪些安全改进? 3. 能否在不破坏签名的情况下修改APK资源文件? 4. 如何配置Android Studio实现自动重签名调试包? 5. 为什么Google Play要求新应用使用AAB格式?[^4] [^1]: APK安装流程包含DEX优化和签名验证 [^2]: META-INF目录存储签名证书和文件摘要 [^3]: Android采用自签名证书机制确保应用完整性 [^4]: AAB格式通过模块化减少安装包体积 [^5]: META-INF包含MANIFEST.MF、CERT.SF、CERT.RSA三个签名核心文件
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值