Android加密算法之非对称加密RSA

上文《Android加密算法之对称加密AES》提到与对称加密算法相对应的非对称加密RSA,本文正式进入该算法的简单讲解和使用。

非对称加密算法:加密和解密使用不同密钥的加密算法,也称为公私钥加密。不同的秘钥指作公开密钥(publickey)和私有秘钥(privatekey),两个天生一对,密不可分。如果用公钥对数据进行加密,只有用对应的私钥才能解密,如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。

上述可能会给小伙伴们一个很友好的疑问,都可以互相加解密,为什么有公私之分,随便给两个密钥公私身份可以吗?

首先从长度上来比较,私钥长度明显比公钥高不少,这意味着对密钥的记忆难度有着很大差别,另外在逆向破解中,由于该算法特性,长度短的公钥想逆向推导出私钥的代价是相当昂贵的,相反私钥推导出公钥相对来说简单到爆炸,因此我们会本能的想到,长度短的密钥公开给众人使用解开自己的公布的密文,而长度长的密钥由自己私有保管。

其次从非对称加密的设计本意来说:公钥加密,私钥解密;私钥加签,公钥验签。设想A和B两位是好友,A持有公钥PK-A和私钥Sk-A,B持有公钥PK-B和私钥SK-B,PK-A和PK-B都是公开的,全世界人都知道,而SK-A和SK-B分别只有A和B自己知道。分析以下场景:

场景1:A给B发信息,在通信过程中,信息内容可能被非法之徒劫持,如果A没有作加密处理,那就.....后果可想而知,那么A会选择用什么密钥加密?我们逐一排除,选择PK-A?加密完后,只有SK-A解密,这倒好了,劫持者不知道SK-A,无法解密就算了,B也不知道啊,也解不了,PASS掉!选SK-A?OK,那么只能PK-A解密,而B和劫持者都知道PK-A,都能解密,这跟没加密有什么区别?狠狠的PASS掉!选SK-B?你想多了,只有B知道SK-B啊,没得选,只能选PK-B,那么只能用SK-B解密了,劫持者并不知道SK-B,只能干看着密文着急,而B能轻松解密并给查看A给自己发的内容了。

场景2:A对B突发好感,给其写了一封匿名电子情书,B看了后很感动但是以为是C写的,于是向作为好友的A分享自己的喜悦,这时候A着急了,说是自己写的,B就是不相信,以为在逗他。没办法,A只能出大招,高度自信地告诉B把这封信上的一处签名解开就是A的名字,那么问题来了,A用什么密钥来制作签名的?为啥这么自信?PK-B?,全世界人都可以制作这个签名,挂掉!SK-B?没人知道啊,怎么制作啊?毙掉!PK-A?别人也知道啊,也可以制作这个签名啊,那么只能SK-A了,只有A能做这样的签名,这时候B拿A的PK-A验证这个签名,验证的结果正是A的姓名。从此二人过上了愉快的基友生活.....咦。。。。。。。。

相信上面两个场景,会让小伙伴们感悟颇深,无法忘怀,理解为重,理解为重哈。。。

 

回到主题,之前文章《Android加密算法之对称加密AES》我们知道,对称加密算法具有算法公开、计算量小、加密速度快、加密效率高等优点,非对称加密算法则比之慢数千倍,这一点笔者在做RSA加解密的时候深有感触,这里提醒Android Developer在做非对称加密的时候最好能在子线程中执行,否则容易造成ANR。话说回来,非对称加密算法在保护通信安全方面,具有对称加密难以企及的优势,具体以后文章会更新拓展。

非对称加密主要使用的是RSA算法,Android RSA算法一样沿用java的API,公私钥长度建议为2048位(至少1024及以上,以下已被破解),推荐"RSA/ECB/OAEPWithSHA256AndMGF1Padding"填充方式,否则容易被重放攻击。下面为RSA实现工具类RSAUtils.java :

package cn.icheny.security;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
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.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import android.util.Base64;

/**
 * RSA加密,解密工具
 * 
 * @author Cheny
 */

public class RSAUtils {
    // 推荐的RSA加密模式,否则容易被重放攻击
    private static final String RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING = "RSA/ECB/OAEPWithSHA256AndMGF1Padding";
    private static final String RSA = "RSA";

    /**
     * 生成秘钥,公钥和私钥
     * 
     * @return Base64编码的公钥和私钥 secretKeys[]{rsaPublicKey,rsaPrivateKey}
     */
    public static String[] generateSecretKey() {
        String[] secretKeys = null;
        try {
            // 构建密钥对生成器
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
            // 初始化秘钥长度 2048位(512位已被破解,至少用1024及以上)
            keyPairGenerator.initialize(2048);

            // 生成密钥对,包含公钥私钥
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 公钥对象,RSAPublicKey为PublicKey子接口
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            // 公钥对象,RSAPrivateKey为PrivateKey子接口
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();

            // 公钥
            byte[] publicKeyBytes = rsaPublicKey.getEncoded();
            // 私钥
            byte[] privateKeyBytes = rsaPrivateKey.getEncoded();

            secretKeys = new String[2];
            // 对公私钥Base64转码,加解密时需要把转码后的公私钥进行Base64还原
            secretKeys[0] = Base64.encodeToString(publicKeyBytes, Base64.DEFAULT);
            secretKeys[1] = Base64.encodeToString(privateKeyBytes, Base64.DEFAULT);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return secretKeys;
    }

    /**
     * 公钥加密
     * 
     * @param plaintext 明文
     * @param rsaPublicKey 公钥
     * @return Base64编码的密文
     */
    public static String encryptByPublicKey(String plaintext, String rsaPublicKey) {
        try {
            // Base64还原公钥
            byte[] publicKeyBytes = Base64.decode(rsaPublicKey, Base64.DEFAULT);
            // X509编码秘钥规范
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
            // 秘钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 还原公钥对象,PublicKey为RSAPublicKey父接口
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);

            // Cipher对象,指定算法模式
            Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
            // 初始化,指定为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 执行加密
            byte[] result = cipher.doFinal(plaintext.getBytes("UTF-8"));
            return Base64.encodeToString(result, Base64.DEFAULT); // 对密文Base64转码,解密时需要把转码后的密文进行Base64还原
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公钥解密
     * 
     * @param ciphertext 密文
     * @param rsaPublicKey 公钥
     * @return Base64编码的密文
     */
    public static String decryptByPublicKey(String ciphertext, String rsaPublicKey) {
        try {
            // Base64还原公钥
            byte[] publicKeyBytes = Base64.decode(rsaPublicKey, Base64.DEFAULT);
            // X509秘钥编码规范
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
            // 秘钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 还原公钥对象,PublicKey为RSAPublicKey父接口
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);

            // Cipher对象,指定算法模式
            Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
            // 初始化,指定为解密模式
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            // Base64还原密文
            byte[] cipherBytes = Base64.decode(ciphertext, Base64.DEFAULT);
            // 执行解密
            byte[] result = cipher.doFinal(cipherBytes);
            return new String(result, "UTF-8");
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥加密
     * 
     * @param plaintext  明文
     * @param rsaPrivateKey 私钥
     * @return Base64编码的密文
     */
    public static String encryptByPrivateKey(String plaintext, String rsaPrivateKey) {
        try {
            // Base64还原私钥
            byte[] privateKeyBytes = Base64.decode(rsaPrivateKey, Base64.DEFAULT);
            // PKCS8秘钥编码规范
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            // 秘钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 还原公钥对象,PrivateKey为RSAPrivateKey父接口
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

            // Cipher对象,指定算法模式
            Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
            // 初始化,指定为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            // 执行加密
            byte[] result = cipher.doFinal(plaintext.getBytes("UTF-8"));
            return Base64.encodeToString(result, Base64.DEFAULT);// 对密文Base64转码,解密时需要把转码后的密文进行Base64还原
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     * 
     * @param ciphertext  密文
     * @param rsaPrivateKey 秘钥
     * @return 明文
     */
    public static String decryptByPrivateKey(String ciphertext, String rsaPrivateKey) {

        try {
            // Base64还原私钥
            byte[] privateKeyBytes = Base64.decode(rsaPrivateKey, Base64.DEFAULT);
            // PKCS8秘钥编码规范
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            // 秘钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            // 还原公钥对象,PrivateKey为RSAPrivateKey父接口
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

            // Cipher对象,指定算法模式
            Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
            // 初始化,指定为解密模式
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // Base64还原密文
            byte[] cipherBytes = Base64.decode(ciphertext, Base64.DEFAULT);
            // 执行解密
            byte[] result = cipher.doFinal(cipherBytes);
            return new String(result, "UTF-8");
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

 

个人习惯,代码中注释得很详细,所以就不在此话似唐僧了,与其一边看代码,一边看代码下文的解释让人视觉疲劳,还不如把注释写好,笔者深有体会。文章先到这,日后会继续更新。。。

© 版权声明
THE END
喜欢就支持以下吧
点赞1 分享
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容