RSA非对称加密(以Android和java为例)

一.概念

1.1目的

由于工作中用到了趟过了许多的坑所以深有体会,所以想写个笔记怕以后忘记了,好到时候回来看看。

首先是一定要知道最基础的概念,一定要知道最基础的概念,一定要知道最基础的概念,以免被误导。

1.2具体概念(就俩RSA加密和校验)

在这里摘抄百度百科中的要点,具体的请自行百度或google。
1.2.1 RSA:RSA算法是一种非对称密码算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密(这俩不同哦)。这里分为公钥(pk)和秘钥(sk),这里使用pk加密,sk解密(其实也可以sk加密,pk解密,但是前者更加的安全所以使用前者)。

可以理解为:实际使用时候公钥加密,私钥解密

1.2.2 RSA既能用于数据加密也能用于数字签名的算法。
1.2.3 数字签名:数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名,就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。

可以理解为:私钥进行签名,公钥负责校验,验证信息提供者有效身份。

1.2如何使用(理论):

1.2.1先来说一下限制条件:

a.生成密文的长度和明文长度无关,但明文长度不能超过密钥长度(不管明文长度是多少,RSA 生成的密文长度总是固定的。但是明文长度不能超过密钥长度。比如 Java 默认的 RSA 加密实现不允许明文长度超过密钥长度减去 11(单位是字节,也就是 byte)。也就是说,如果我们定义的密钥(我们可以通过 java.security.KeyPairGenerator.initialize(int keysize) 来定义密钥长度)长度为 1024(单位是位,也就是 bit),生成的密钥长度就是 1024位 / 8位/字节 = 128字节,那么我们需要加密的明文长度不能超过 128字节 -11 字节 = 117字节。也就是说,我们最大能将 117 字节长度的明文进行加密,否则会出问题(抛诸如 javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes 的异常)。);
b.生成密文的长度等于密钥长度

使用RSA加密时候的各种坑连接

1.2.2加密没有唯一方案可言选择适合的方案就好。通常我们选用对称加密和非对称加密结合的方案(对称选AES,DES,3DES;非对称用RSA);

分情况讨论

第一种情况:被加密的数据量很小直接用RSA
第二种情况:加密的数据量较大但是对称加密不放心,使用AES,DES,3DES中一种和RSA结合方式,具体数据用对称加密一种加密,而对称加密的秘钥用RSA加密传给服务端,服务端则先RSA解密出对称加密的秘钥,再用它解密已经加密的数据;
第三种情况:要校验信息来源的真实性(是不是服务端传来的“原始数据”),使用RSA的私钥签名,公钥校验;

二实现代码(用Android端和java后台)

点击完整代码下载

2.1各种工具类,下面代码齐全,copy就能用(不懂仔细看代码中注释很详细)

加密解密工具类

/**
 * Created by gxo on 2017/11/29.
 * 加密解密工具类
 */
public class RSAEncrypt {

    /**
     * 公钥加密过程
     * @param plainTextData
     *            明文数据
     * @return  密文数据
     * @throws Exception
     *             加密过程中的异常信息
     */
    public static byte[] encryptByPublicKey(byte[] plainTextData) throws Exception {
        RSAPublicKey publicKey = RsaKeyUtil.loadPublicKey();

        if (publicKey == null) {
            throw new Exception("加密公钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            // 使用默认RSA
            cipher = Cipher.getInstance("RSA");
            // cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] output = cipher.doFinal(plainTextData);
            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("明文数据已损坏");
        }
    }

    /**
     * 私钥加密过程
     * @param plainTextData
     *            明文数据
     * @return 密文数据
     * @throws Exception
     *             加密过程中的异常信息
     */
    public static byte[] encryptByPrivateKey(byte[] plainTextData) throws Exception {
        RSAPrivateKey privateKey = RsaKeyUtil.loadPrivateKey();
        if (privateKey == null) {
            throw new Exception("加密私钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            // 使用默认RSA
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(plainTextData);
            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("明文数据已损坏");
        }
    }

    /**
     * 私钥解密过程
     * @param cipherData 密文数据
     * @return 明文
     * @throws Exception
     *             解密过程中的异常信息
     */
    public static byte[] decryptByPrivateKey(byte[] cipherData) throws Exception {
        RSAPrivateKey privateKey = RsaKeyUtil.loadPrivateKey();

        if (privateKey == null) {
            throw new Exception("解密私钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            // 使用默认RSA
            cipher = Cipher.getInstance("RSA");
            // 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("密文数据已损坏");
        }
    }

    /**
     * 公钥解密过程
     * @param cipherData
     *            密文数据
     * @return 明文
     * @throws Exception
     *             解密过程中的异常信息
     */
    public static byte[] decryptByPublicKey( byte[] cipherData) throws Exception {
        RSAPublicKey publicKey = RsaKeyUtil.loadPublicKey();
        if (publicKey == null) {
            throw new Exception("解密公钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            // 使用默认RSA
            cipher = Cipher.getInstance("RSA");
            // cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            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("密文数据已损坏");
        }
    }
}

秘钥管理类

/**
 * Created by gxo on 2017/11/30.
 * 秘钥管理类
 */

public class RsaKeyUtil {
    /**
     * 随机生成密钥对
     */
    public static void genKeyPair(Handler handler) {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 初始化密钥对生成器,密钥大小为96-1024位(这里是常用值)
        keyPairGen.initialize(1024,new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        try {
            // 得到公钥字符串
            String publicKeyString = Base64.encode(publicKey.getEncoded());
            // 得到私钥字符串
            String privateKeyString = Base64.encode(privateKey.getEncoded());
            Log.e("publicKeyString",publicKeyString);
            Log.e("privateKeyString",privateKeyString);
            // 将密钥对写入到文件(文件后缀不重要只要系统能识别的文件都行,只是为了存储用)
            String publicKeyPath = FIleUtils.getPublicKeyPath();
            String privateKeyPath = FIleUtils.getPrivateKeyPath();
            FileWriter pubfw = new FileWriter(publicKeyPath);
            FileWriter prifw = new FileWriter(privateKeyPath);
            BufferedWriter pubbw = new BufferedWriter(pubfw);
            BufferedWriter pribw = new BufferedWriter(prifw);
            pubbw.write(publicKeyString);
            pribw.write(privateKeyString);
            pubbw.flush();
            pubbw.close();
            pubfw.close();
            pribw.flush();
            pribw.close();
            prifw.close();

            handler.sendEmptyMessage(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载公钥
     */
    public static RSAPublicKey loadPublicKey() throws Exception {
        String publicKeyStr = loadPublicKeyByFile();
        try {
            byte[] buffer = Base64.decode(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    /**
     * 从文件中加载公钥
     * @throws Exception 加载公钥时产生的异常
     */
    public static String loadPublicKeyByFile() throws Exception {
        try {
            BufferedReader br = new BufferedReader(new FileReader(FIleUtils.getPublicKeyPath()));
            String readLine = null;
            StringBuilder sb = new StringBuilder();
            while ((readLine = br.readLine()) != null) {
                sb.append(readLine);
            }
            br.close();
            Log.e("publicKeyStr",sb.toString());
            return sb.toString();
        } catch (IOException e) {
            throw new Exception("公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new Exception("公钥输入流为空");
        }
    }

    public static RSAPrivateKey loadPrivateKey() throws Exception {
        String privateKeyStr = loadPrivateKeyByFile();
        try {
            byte[] buffer = Base64.decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 从文件中加载私钥
     * @throws Exception
     */
    public static String loadPrivateKeyByFile() throws Exception {
        try {
            BufferedReader br = new BufferedReader(new FileReader(FIleUtils.getPrivateKeyPath()));
            String readLine = null;
            StringBuilder sb = new StringBuilder();
            while ((readLine = br.readLine()) != null) {
                sb.append(readLine);
            }
            br.close();
            Log.e("privateKeyStr",sb.toString());
            return sb.toString();
        } catch (IOException e) {
            throw new Exception("私钥数据读取错误");
        } catch (NullPointerException e) {
            throw new Exception("私钥输入流为空");
        }
    }
}

文件处理类

/**
 * Created by gxo on 2017/11/29.
 * 文件处理类
 */

public class FIleUtils {
    public static String getPrivateKeyPath(){
        String rootPath = getRootPath();
        String filePath = rootPath + "/privateKey.keystore";
        File file = new File(filePath);
        if (!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return filePath;
    }

    public static String getPublicKeyPath(){
        String rootPath = getRootPath();
        String filePath = rootPath + "/publicKey.keystore";
        File file = new File(filePath);
        if (!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return filePath;
    }

    private static String getRootPath() {
        String filepath= Environment.getExternalStorageDirectory().getPath() + File.separator + "mykey"+ File.separator + "pripub";

        File file = new File(filepath);
        if (!file.exists()) {
            file.mkdirs();
        }

        return filepath;
    }
}

签名和校验类

/**
 * Created by gxo on 2017/11/29.
 * 签名和校验类
 */

public class RSASignature {
    /**
     * 签名算法
     */
    public static final String SIGN_ALGORITHMS = "SHA1WithRSA";

    /**
     * RSA签名
     * @param content 待签名数据
     * @param privateKey 商户私钥
     * @param encode 字符集编码
     * @return 签名值
     */
    public static String sign(String content, String privateKey, String encode)
    {
        try
        {
            PKCS8EncodedKeySpec priPKCS8    = new PKCS8EncodedKeySpec( Base64.decode(privateKey) );

            KeyFactory keyf                 = KeyFactory.getInstance("RSA");
            PrivateKey priKey               = keyf.generatePrivate(priPKCS8);

            java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);

            signature.initSign(priKey);
            signature.update( content.getBytes(encode));

            byte[] signed = signature.sign();

            return Base64.encode(signed);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return null;
    }

    public static String sign(String content) {
        try {
            String privateKey = RsaKeyUtil.loadPrivateKeyByFile();
            PKCS8EncodedKeySpec priPKCS8    = new PKCS8EncodedKeySpec( Base64.decode(privateKey) );
            KeyFactory keyf = KeyFactory.getInstance("RSA");
            PrivateKey priKey = keyf.generatePrivate(priPKCS8);
            java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
            signature.initSign(priKey);
            signature.update( content.getBytes());
            byte[] signed = signature.sign();
            return Base64.encode(signed);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean doCheck(String content, String sign) {
        try {
            String publicKey = RsaKeyUtil.loadPublicKeyByFile();
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decode(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            java.security.Signature signature = java.security.Signature
                    .getInstance(SIGN_ALGORITHMS);

            signature.initVerify(pubKey);
            signature.update( content.getBytes() );

            boolean bverify = signature.verify( Base64.decode(sign) );
            return bverify;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return false;
    }
}

测试入口

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        testRsa();
    }

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg != null){
                int what = msg.what;
                switch (what){
                    case 1:
                        publicEncryptPrivateDecrypt();//验证公钥加密私钥解密
                        //privateEncryptPublicDecrypt();//验证私钥加密公钥解密
                        //signAndCheck();//验证签名和校验
                        break;
                }
            }
        }
    };

    private void signAndCheck() {
        System.out.println("---------------私钥签名过程------------------");
        String content="ihep_这是用于签名的原始数据";
        String signstr= null;
        try {
            signstr = RSASignature.sign(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("签名原串:"+content);
        System.out.println("签名串:"+signstr);
        System.out.println();

        System.out.println("---------------公钥校验签名------------------");
        System.out.println("签名原串:"+content);
        System.out.println("签名串:"+signstr);

        try {
            System.out.println("验签结果:"+RSASignature.doCheck(content, signstr));
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println();
    }

    private void privateEncryptPublicDecrypt() {
        System.out.println("--------------私钥加密公钥解密过程-------------------");

        String plainText="ihep_私钥加密公钥解密";
        //私钥加密过程
        byte[] cipherData= new byte[0];
        try {
            cipherData=RSAEncrypt.encryptByPrivateKey(plainText.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
        String cipher=Base64.encode(cipherData);
        //公钥解密过程
        byte[] res= new byte[0];
        try {
            res=RSAEncrypt.decryptByPublicKey(Base64.decode(cipher));
        } catch (Exception e) {
            e.printStackTrace();
        }
        String restr=new String(res);
        System.out.println("原文:"+plainText);
        System.out.println("加密:"+cipher);
        System.out.println("解密:"+restr);
        System.out.println();
    }

    private void publicEncryptPrivateDecrypt() {
        System.out.println("--------------公钥加密私钥解密过程-------------------");
        String plainText="公钥加密私钥解密过程";
        //公钥加密过程
        byte[] cipherData= new byte[0];
        try {
            cipherData = RSAEncrypt.encryptByPublicKey(plainText.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //生成可以传送的字符串
        String cipher=Base64.encode(cipherData);
        //私钥解密过程
        byte[] res= new byte[0];
        try {
            res = RSAEncrypt.decryptByPrivateKey(Base64.decode(cipher));
        } catch (Exception e) {
            e.printStackTrace();
        }
        String restr=new String(res);
        System.out.println("原文:"+plainText);
        System.out.println("加密:"+cipher);
        System.out.println("解密:"+restr);
        System.out.println();
    }

    private void testRsa() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //生成公钥和私钥文件并保存
                RsaKeyUtil.genKeyPair(handler);
            }
        }).start();
    }
}

Base64管理类

/**
 * Created by gxo on 2017/11/29.
 * Base64管理类
 */

public class Base64 {
    static private final int     BASELENGTH           = 128;
    static private final int     LOOKUPLENGTH         = 64;
    static private final int     TWENTYFOURBITGROUP   = 24;
    static private final int     EIGHTBIT             = 8;
    static private final int     SIXTEENBIT           = 16;
    static private final int     FOURBYTE             = 4;
    static private final int     SIGN                 = -128;
    static private final char    PAD                  = '=';
    static private final boolean fDebug               = false;
    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];

    static {
        for (int i = 0; i < BASELENGTH; ++i) {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--) {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i >= 'a'; i--) {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--) {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++) {
            lookUpBase64Alphabet[i] = (char) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('0' + j);
        }
        lookUpBase64Alphabet[62] = (char) '+';
        lookUpBase64Alphabet[63] = (char) '/';
    }

    private static boolean isWhiteSpace(char octect) {
        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
    }

    private static boolean isPad(char octect) {
        return (octect == PAD);
    }

    private static boolean isData(char octect) {
        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
    }

    /**
     * Encodes hex octects into Base64
     *
     * @param binaryData Array containing binaryData
     * @return Encoded Base64 array
     */
    public static String encode(byte[] binaryData) {

        if (binaryData == null) {
            return null;
        }

        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0) {
            return "";
        }

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
        char encodedData[] = null;

        encodedData = new char[numberQuartet * 4];

        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;

        int encodedIndex = 0;
        int dataIndex = 0;
        if (fDebug) {
            System.out.println("number of triplets = " + numberTriplets);
        }

        for (int i = 0; i < numberTriplets; i++) {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];

            if (fDebug) {
                System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3);
            }

            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);

            if (fDebug) {
                System.out.println("val2 = " + val2);
                System.out.println("k4   = " + (k << 4));
                System.out.println("vak  = " + (val2 | (k << 4)));
            }

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }

        // form integral number of 6-bit groups
        if (fewerThan24bits == EIGHTBIT) {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);
            if (fDebug) {
                System.out.println("b1=" + b1);
                System.out.println("b1<<2 = " + (b1 >> 2));
            }
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        } else if (fewerThan24bits == SIXTEENBIT) {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }

        return new String(encodedData);
    }

    /**
     * Decodes Base64 data into octects
     *
     * @param encoded string containing Base64 data
     * @return Array containind decoded data.
     */
    public static byte[] decode(String encoded) {
        if (encoded == null) {
            return null;
        }

        char[] base64Data = encoded.toCharArray();
        // remove white spaces
        int len = removeWhiteSpace(base64Data);

        if (len % FOURBYTE != 0) {
            return null;//should be divisible by four
        }

        int numberQuadruple = (len / FOURBYTE);

        if (numberQuadruple == 0) {
            return new byte[0];
        }

        byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;

        int i = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        decodedData = new byte[(numberQuadruple) * 3];

        for (; i < numberQuadruple - 1; i++) {

            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
                    || !isData((d3 = base64Data[dataIndex++]))
                    || !isData((d4 = base64Data[dataIndex++]))) {
                return null;
            }//if found "no data" just return null

            b1 = base64Alphabet[d1];
            b2 = base64Alphabet[d2];
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];

            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
        }

        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
            return null;//if found "no data" just return null
        }

        b1 = base64Alphabet[d1];
        b2 = base64Alphabet[d2];

        d3 = base64Data[dataIndex++];
        d4 = base64Data[dataIndex++];
        if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {
                if ((b2 & 0xf) != 0)//last 4 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 1];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
                return tmp;
            } else if (!isPad(d3) && isPad(d4)) {
                b3 = base64Alphabet[d3];
                if ((b3 & 0x3) != 0)//last 2 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 2];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
                return tmp;
            } else {
                return null;
            }
        } else { //No PAD e.g 3cQl
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];
            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

        }

        return decodedData;
    }

    /**
     * remove WhiteSpace from MIME containing encoded Base64 data.
     *
     * @param data  the byte array of base64 data (with WS)
     * @return      the new length
     */
    private static int removeWhiteSpace(char[] data) {
        if (data == null) {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++) {
            if (!isWhiteSpace(data[i])) {
                data[newSize++] = data[i];
            }
        }
        return newSize;
    }
}

2.2工具类的说明

2.2.1私钥加密和公钥解密只是为了说明可以实现,但是实际不能这么写
2.2.2产生秘钥对的方式有很多种,这里为了方便在Android代码中生成放在文件中;实际生产中可以使用线上的工具产生,也可以下载工具在本地产生,也可以在java后台产生;使用的时候可以把秘钥字符串放在java代码中也可以放在文件中。(不知道怎么找可以百度)
2.2.3代码中io操作的有的地方应放在子线程中,这里为了偷懒

2.3下面依次是上面三种测试的log(知道可以用了吧!哈哈)

2.3.1公钥加密私钥解密
12-01 08:46:11.333 14150-14189/com.rsa.test E/publicKeyString: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC80bTu4sQOt35PRCTlmXhAciTINBxe+bM9Qx7wz11k5xTzZbjbpEZaAlfurKohM8hWCblEJ7qE02N3NaY3enbNec1IAW67YchE41Us81nMUIvGg91eFpGj9Sf8WkVRvvNjmGaJMjQ2ufEzM5DUDysR44RxTu1aCrdCZs9ZqRESeQIDAQAB
12-01 08:46:11.333 14150-14189/com.rsa.test E/privateKeyString: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALzRtO7ixA63fk9EJOWZeEByJMg0HF75sz1DHvDPXWTnFPNluNukRloCV+6sqiEzyFYJuUQnuoTTY3c1pjd6ds15zUgBbrthyETjVSzzWcxQi8aD3V4WkaP1J/xaRVG+82OYZokyNDa58TMzkNQPKxHjhHFO7VoKt0Jmz1mpERJ5AgMBAAECgYEAjfycNG8tw2tUfkLeBBGuDbhH1IzrMLhQgqbwo2Er9+6VIJrDsdcMWC8e9Im+vYz0/038lTXS2oMjbsdXIKzknD5k7BAFVimXNNzCJryhS7MmzO+VSeEDE2LdWxMdpXDKNb4iKInuxRfXAeIsor0LIL7Da8HvU0DgfF6V1Q9XnUkCQQD1m+77rhJSpL7aOjCEQMPCuz+TjH23Y+5Y0fSbnHxMCqb/nPd4jXvg6FJCe6d+eBr/61DAlmlr1+Wjcuvi7VwTAkEAxM613aH1mhOgMhs09R4/QC8Wy7wDNy+D22+meo38OBO0ILr9XfsNuBxex6M12C1pmL5BY6JpROnz7j8fbEpQwwJBALt/iibKkaRTrntMduNPyeJsLjHGy+ZnvHpD9hehk+Y9L5Xtz6JRaGOfF8U3Pvu2Vu5ORzxu1xYEIIPx5JA9oc8CQBAT+xOSKahethWh11+3Ct/JeQlH4NNfVFxW8LjOZK6IHY0W+Z/15JcH6k5Q7RMuEpRh5qGnIQZ6Pm1WRtDn+VUCQFHQpCYW6kVYh7eAm1kPZ0e1CBBJEHRaqmuLaNy79hQWWLYO1R9eMfbMYVjXlxSS/7RWrZ8zthiZ/D7PL3NXVwM=

12-01 08:46:11.337 14150-14150/com.rsa.test I/System.out: --------------公钥加密私钥解密过程-------------------
12-01 08:46:11.338 14150-14150/com.rsa.test E/publicKeyStr: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC80bTu4sQOt35PRCTlmXhAciTINBxe+bM9Qx7wz11k5xTzZbjbpEZaAlfurKohM8hWCblEJ7qE02N3NaY3enbNec1IAW67YchE41Us81nMUIvGg91eFpGj9Sf8WkVRvvNjmGaJMjQ2ufEzM5DUDysR44RxTu1aCrdCZs9ZqRESeQIDAQAB
12-01 08:46:11.342 14150-14150/com.rsa.test E/privateKeyStr: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALzRtO7ixA63fk9EJOWZeEByJMg0HF75sz1DHvDPXWTnFPNluNukRloCV+6sqiEzyFYJuUQnuoTTY3c1pjd6ds15zUgBbrthyETjVSzzWcxQi8aD3V4WkaP1J/xaRVG+82OYZokyNDa58TMzkNQPKxHjhHFO7VoKt0Jmz1mpERJ5AgMBAAECgYEAjfycNG8tw2tUfkLeBBGuDbhH1IzrMLhQgqbwo2Er9+6VIJrDsdcMWC8e9Im+vYz0/038lTXS2oMjbsdXIKzknD5k7BAFVimXNNzCJryhS7MmzO+VSeEDE2LdWxMdpXDKNb4iKInuxRfXAeIsor0LIL7Da8HvU0DgfF6V1Q9XnUkCQQD1m+77rhJSpL7aOjCEQMPCuz+TjH23Y+5Y0fSbnHxMCqb/nPd4jXvg6FJCe6d+eBr/61DAlmlr1+Wjcuvi7VwTAkEAxM613aH1mhOgMhs09R4/QC8Wy7wDNy+D22+meo38OBO0ILr9XfsNuBxex6M12C1pmL5BY6JpROnz7j8fbEpQwwJBALt/iibKkaRTrntMduNPyeJsLjHGy+ZnvHpD9hehk+Y9L5Xtz6JRaGOfF8U3Pvu2Vu5ORzxu1xYEIIPx5JA9oc8CQBAT+xOSKahethWh11+3Ct/JeQlH4NNfVFxW8LjOZK6IHY0W+Z/15JcH6k5Q7RMuEpRh5qGnIQZ6Pm1WRtDn+VUCQFHQpCYW6kVYh7eAm1kPZ0e1CBBJEHRaqmuLaNy79hQWWLYO1R9eMfbMYVjXlxSS/7RWrZ8zthiZ/D7PL3NXVwM=
12-01 08:46:11.351 14150-14150/com.rsa.test I/System.out: 原文:公钥加密私钥解密过程
12-01 08:46:11.351 14150-14150/com.rsa.test I/System.out: 加密:uQ0UIGF2TunqQHnW7E8li8wSvhK8IS+mKufpZ1qtove3W5yCv406T7tLyu1BXpaf17+uj2kefYGwqiAxIHnRjcqaTs13Wl6yh5K0W3t2VCvd697EYo2kXjXfpCICkVlOYXUYyHItre7FB48KNXsw2DV94ttytuO1igyixYzFl8A=
12-01 08:46:11.351 14150-14150/com.rsa.test I/System.out: 解密:公钥加密私钥解密过程
2.3.2私钥加密公钥解密
12-01 08:49:09.557 16713-16743/com.rsa.test E/publicKeyString: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDB2ZDYqyQ/3Wdsa+E+QvQkB1kunl7zxXezDwaHJTQoOGNCSJFGG8rMHTblrvOIlmyHgXGNbFA4UWZrbzWUHEhzzHO3L088tF+Z4h8TECL1DDHLALgm8p20tUzR/qMaIfeOsh6N9D1IsQ9P10SJlHV8w+SCrB0J2vh/m73QZWKTdQIDAQAB
12-01 08:49:09.557 16713-16743/com.rsa.test E/privateKeyString: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMHZkNirJD/dZ2xr4T5C9CQHWS6eXvPFd7MPBoclNCg4Y0JIkUYbyswdNuWu84iWbIeBcY1sUDhRZmtvNZQcSHPMc7cvTzy0X5niHxMQIvUMMcsAuCbynbS1TNH+oxoh946yHo30PUixD0/XRImUdXzD5IKsHQna+H+bvdBlYpN1AgMBAAECgYEAo6QiXoW6yB8DW8eGEaU7q954Lp6K0YZ1Ebcxb4y8hudzA1V3LZPwknZnDWWvODKj05A2Z4hz/9bXewIaXLmzfnKfpGaSuGFPg4ldaxJMIZpuGGP8XQonkhGxwwf5EZyIB3b9JVu4bEDydJWYQDUT/iTzgQpSZQg5aGQ6jFJMpgECQQD8bwMDGFUXYp5fD9dAsAf0XkJ5DsKnGoSSIAcs7Ys6m9Dpo33Q4tfKC8OLfx67OPZisZd5MgyapEK484Y3z+TVAkEAxJar4403vTtMgQAli4+iYNiltY+V1RHRiAN5+1E7N2vc4g66NvI8EiomqXqmjY01uyCRJLlZyO8LpN0ONFfEIQJBALWuMDzKBnQORm2kuQeVqiP2s2DtfqtGKqQA6SkszkggXPvK74KnwSdp0BNWKogVNz9REfPoUan7DwqOOstwlQECQExmujrfYTA74YmpAu5wTg0RZaVgPjzmheY0Y63Wbgsl8lwwmRJNpibxFsF7F0uf+6fUClSZbFl4/a9+8cxJ58ECQGkdj1rBuLjqOG/1I/mgiKno4M7+SYDhkBX171kKWOrQwoSYSrMTOnhTC249VMIEsnjjY59Tfuj+ebpA2y4SlVA=
12-01 08:49:09.559 16713-16713/com.rsa.test I/System.out: --------------私钥加密公钥解密过程-------------------
12-01 08:49:09.561 16713-16713/com.rsa.test E/privateKeyStr: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMHZkNirJD/dZ2xr4T5C9CQHWS6eXvPFd7MPBoclNCg4Y0JIkUYbyswdNuWu84iWbIeBcY1sUDhRZmtvNZQcSHPMc7cvTzy0X5niHxMQIvUMMcsAuCbynbS1TNH+oxoh946yHo30PUixD0/XRImUdXzD5IKsHQna+H+bvdBlYpN1AgMBAAECgYEAo6QiXoW6yB8DW8eGEaU7q954Lp6K0YZ1Ebcxb4y8hudzA1V3LZPwknZnDWWvODKj05A2Z4hz/9bXewIaXLmzfnKfpGaSuGFPg4ldaxJMIZpuGGP8XQonkhGxwwf5EZyIB3b9JVu4bEDydJWYQDUT/iTzgQpSZQg5aGQ6jFJMpgECQQD8bwMDGFUXYp5fD9dAsAf0XkJ5DsKnGoSSIAcs7Ys6m9Dpo33Q4tfKC8OLfx67OPZisZd5MgyapEK484Y3z+TVAkEAxJar4403vTtMgQAli4+iYNiltY+V1RHRiAN5+1E7N2vc4g66NvI8EiomqXqmjY01uyCRJLlZyO8LpN0ONFfEIQJBALWuMDzKBnQORm2kuQeVqiP2s2DtfqtGKqQA6SkszkggXPvK74KnwSdp0BNWKogVNz9REfPoUan7DwqOOstwlQECQExmujrfYTA74YmpAu5wTg0RZaVgPjzmheY0Y63Wbgsl8lwwmRJNpibxFsF7F0uf+6fUClSZbFl4/a9+8cxJ58ECQGkdj1rBuLjqOG/1I/mgiKno4M7+SYDhkBX171kKWOrQwoSYSrMTOnhTC249VMIEsnjjY59Tfuj+ebpA2y4SlVA=
12-01 08:49:09.573 16713-16713/com.rsa.test E/publicKeyStr: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDB2ZDYqyQ/3Wdsa+E+QvQkB1kunl7zxXezDwaHJTQoOGNCSJFGG8rMHTblrvOIlmyHgXGNbFA4UWZrbzWUHEhzzHO3L088tF+Z4h8TECL1DDHLALgm8p20tUzR/qMaIfeOsh6N9D1IsQ9P10SJlHV8w+SCrB0J2vh/m73QZWKTdQIDAQAB
12-01 08:49:09.574 16713-16713/com.rsa.test I/System.out: 原文:ihep_私钥加密公钥解密
12-01 08:49:09.574 16713-16713/com.rsa.test I/System.out: 加密:k1u/ChmolNypfIlVRu+uuc8Qayf5I9cj9ufONBAxl4agonPfjtxxyYRvwNPPFQMPWMAd2rpVwzvSvzHWIJpOiqnppdj+xkJHyr8JQVLaYZDEGc8jck9YPMoJsdDvnOCwLN19b8XoP2nt1fCeNZPVJ2HDzlBWcO29kc2cE5HRlf4=
12-01 08:49:09.574 16713-16713/com.rsa.test I/System.out: 解密:ihep_私钥加密公钥解密
2.3.3签名和校验
12-01 08:52:45.241 18864-18884/com.rsa.test E/publicKeyString: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFxtoFjoWHyyf4QV6NiMxv3GPQALNVgn0NKO+7iREU6OBRTmmYlMainC6HJcIoh1B2AgVGZkuJg2RoFKsd2WzndHDVR/vOwPAIH4cOaYIsEE7WDxW90BlJ0GT9A2kgfZED7TvUQjmSeF4eGU1KiwEeMMGrYC6AnRTzXWUhHkrbbwIDAQAB
12-01 08:52:45.241 18864-18884/com.rsa.test E/privateKeyString: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMXG2gWOhYfLJ/hBXo2IzG/cY9AAs1WCfQ0o77uJERTo4FFOaZiUxqKcLoclwiiHUHYCBUZmS4mDZGgUqx3ZbOd0cNVH+87A8Agfhw5pgiwQTtYPFb3QGUnQZP0DaSB9kQPtO9RCOZJ4Xh4ZTUqLAR4wwatgLoCdFPNdZSEeSttvAgMBAAECgYBe264sJhxjcgwcpPW9upQbPsBcvklFRQ3HZw/8iZQ4zCzyskIxXYIb8f7a8UA7ghMUVonDJ0MplGxvAyLDcUoqq6opde7wFkHyidoikJOFGt3DNk8OYM5sZPpoMgWhbiZ/rqijnrV6sepPEgnKdVW1HGQvLDJOEil5gjOSlFdGAQJBAP2Rjoa2Mo/PiUCDZFNySJ4BimFOvvtE6ou4V73arxcRBHPTIPgRz1JT482784pWO6AI8Y4J3RA4eWtV8jyLTQECQQDHrFdDQHZ3Q/MNI1/7xYRqYwH45fJEDML99YDOX8GRC+LL/27TZwH4duGLOYpJSfjng1fubKYODMhtPcU1zHhvAkB/ArjMe1jKNh8+xFGthrIfCxCAe0woWjXNtJGmlHJdcHW1eoyqFWmuiDy0Ia/0nAsTt0cfaYtkg5U2EX/bmb8BAkB3iDthHjkNItdguRmfvlmL2qN2nimPBVjB8XNfmEFnlIb7+nkdAyBR3tEcqKCZSThBWAaOy+oIpSxjAUSK9VHvAkAODwbyfKDYZlH6P4BNs6UUKr1kuZPewlTZuMv0eHNT4CAdazSUl/mFdfaGA4Dsjy/cHInicMEv8yKp/NPojWO/
12-01 08:52:45.244 18864-18864/com.rsa.test I/System.out: ---------------私钥签名过程------------------
12-01 08:52:45.245 18864-18864/com.rsa.test E/privateKeyStr: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMXG2gWOhYfLJ/hBXo2IzG/cY9AAs1WCfQ0o77uJERTo4FFOaZiUxqKcLoclwiiHUHYCBUZmS4mDZGgUqx3ZbOd0cNVH+87A8Agfhw5pgiwQTtYPFb3QGUnQZP0DaSB9kQPtO9RCOZJ4Xh4ZTUqLAR4wwatgLoCdFPNdZSEeSttvAgMBAAECgYBe264sJhxjcgwcpPW9upQbPsBcvklFRQ3HZw/8iZQ4zCzyskIxXYIb8f7a8UA7ghMUVonDJ0MplGxvAyLDcUoqq6opde7wFkHyidoikJOFGt3DNk8OYM5sZPpoMgWhbiZ/rqijnrV6sepPEgnKdVW1HGQvLDJOEil5gjOSlFdGAQJBAP2Rjoa2Mo/PiUCDZFNySJ4BimFOvvtE6ou4V73arxcRBHPTIPgRz1JT482784pWO6AI8Y4J3RA4eWtV8jyLTQECQQDHrFdDQHZ3Q/MNI1/7xYRqYwH45fJEDML99YDOX8GRC+LL/27TZwH4duGLOYpJSfjng1fubKYODMhtPcU1zHhvAkB/ArjMe1jKNh8+xFGthrIfCxCAe0woWjXNtJGmlHJdcHW1eoyqFWmuiDy0Ia/0nAsTt0cfaYtkg5U2EX/bmb8BAkB3iDthHjkNItdguRmfvlmL2qN2nimPBVjB8XNfmEFnlIb7+nkdAyBR3tEcqKCZSThBWAaOy+oIpSxjAUSK9VHvAkAODwbyfKDYZlH6P4BNs6UUKr1kuZPewlTZuMv0eHNT4CAdazSUl/mFdfaGA4Dsjy/cHInicMEv8yKp/NPojWO/
12-01 08:52:45.253 18864-18864/com.rsa.test I/System.out: 签名原串:ihep_这是用于签名的原始数据
12-01 08:52:45.253 18864-18864/com.rsa.test I/System.out: 签名串:ElHOMjGFvtc500wRpgtFR+i9F63A+G3vh10cwU4EEyNsy0S18O5qYpvv/mWJ+1KvQNd5dY9DNkiBnklhcbFIVbxE4goJfNEHLd1hhVGhytE0ze4QeRsVHnTDA0mXsHuArY8RLYWoDJ96RXoa6VEvpJr6+DUlJ2dPhOqkHehRCIg=
12-01 08:52:45.254 18864-18864/com.rsa.test I/System.out: ---------------公钥校验签名------------------
12-01 08:52:45.254 18864-18864/com.rsa.test I/System.out: 签名原串:ihep_这是用于签名的原始数据
12-01 08:52:45.254 18864-18864/com.rsa.test I/System.out: 签名串:ElHOMjGFvtc500wRpgtFR+i9F63A+G3vh10cwU4EEyNsy0S18O5qYpvv/mWJ+1KvQNd5dY9DNkiBnklhcbFIVbxE4goJfNEHLd1hhVGhytE0ze4QeRsVHnTDA0mXsHuArY8RLYWoDJ96RXoa6VEvpJr6+DUlJ2dPhOqkHehRCIg=
12-01 08:52:45.255 18864-18864/com.rsa.test E/publicKeyStr: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFxtoFjoWHyyf4QV6NiMxv3GPQALNVgn0NKO+7iREU6OBRTmmYlMainC6HJcIoh1B2AgVGZkuJg2RoFKsd2WzndHDVR/vOwPAIH4cOaYIsEE7WDxW90BlJ0GT9A2kgfZED7TvUQjmSeF4eGU1KiwEeMMGrYC6AnRTzXWUhHkrbbwIDAQAB
12-01 08:52:45.256 18864-18864/com.rsa.test I/System.out: 验签结果:true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值