学习目标:了解一些常见的密码学算法 Hex编码 什么是Hex编码 Hex编码是一种用16个字符表示任意二进制数据的方法。它是一种编码,而非加密算法
java代码如下:
package org.example;import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;public class Main { public static void main (String[] args) { String name = "许之少年凌云志, 曾许人间第一流" ; byte [] bytes = name.getBytes(); System.out.println(bytes); String encode = HexBin.encode(bytes); System.out.println(encode); } }
这里注意把编码方式改成UTF-8否则会报错,修改完后要重启idea
url编码与HEX编码大体相同,区别就是在每组字节前面加了个%
encode源码
static public String encode (byte [] binaryData) { if (binaryData == null ) return null ; int lengthData = binaryData.length; int lengthEncode = lengthData * 2 ; char [] encodedData = new char [lengthEncode]; int temp; for (int i = 0 ; i < lengthData; i++) { temp = binaryData[i]; if (temp < 0 ) temp += 256 ; encodedData[i*2 ] = lookUpHexAlphabet[temp >> 4 ]; encodedData[i*2 +1 ] = lookUpHexAlphabet[temp & 0xf ]; } return new String (encodedData); }
在安卓当中使用 导入依赖
implementation ("com.squareup.okhttp3:okhttp:4.9.3")
Log.d("hyq" , "测试代码" ); ByteString byteString = ByteString.of("好有钱" .getBytes());System.out.println(byteString.hex());
Hex编码的特点
用0-9 a-f 16个字符表示
每个十六进制字符代表4bit,也就是2个十六进制字符代表哪一个字节
在实际引用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码写的,采用对应方式解析,才能得到正确结果
手写代码 package org.example;public class HexEncoder { private static final char [] lookupHexAlphabet = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' ,'A' , 'B' , 'C' , 'D' , 'E' , 'F' }; public static String encode (byte [] binaryData) { if (binaryData == null ) return null ; int lengthData = binaryData.length; int lengthEncode = lengthData * 2 ; char [] encodeData = new char [lengthEncode]; int temp; for (int i=0 ; i<lengthData; i++) { temp = binaryData[i]; if (temp < 0 ) temp += 256 ; encodeData[2 *i] = lookupHexAlphabet[temp>> 4 ]; encodeData[2 *i+1 ] = lookupHexAlphabet[temp & 0xF ]; } return new String (encodeData); } public static void main (String[] args) { String name = "好有钱" ; byte [] binaryData = name.getBytes(); System.out.println(encode(binaryData)); } }
Base64 什么是base64 Base64是一种用64个字符表示任意二进制数据的方法。它是一种编码,而非加密算法
A-Z a-z 0-9 + / =
Base64的应用 RSA密钥、加密后的密文、图片等数据中,会有一些不可见字符。直接转换成文本传输的话,会有乱码、数据错误、数据丢失等情况出现,就可以使用Base64编码
Base64编码的实现 java中的实现 public class Main { public static void main (String[] args) { String s = Base64.getEncoder().encodeToString("好有钱12345678" .getBytes(StandardCharsets.UTF_8)); System.out.println(s); } }
android中的实现 第一种,使用ByteString类 需要先导入okhttp3依赖
implementation ("com.squareup.okhttp3:okhttp:4.9.3")
ByteString byteString = ByteString.of("好有钱" .getBytes());System.out.println("byteString base64: " + byteString.base64());
第二种,在android studio中使用java库 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { String s = Base64.getEncoder().encodeToString("好有钱" .getBytes()); byte [] encode = Base64.getEncoder().encode("好有钱" .getBytes()); System.out.println("java.util.Base64" + new String (encode)); System.out.println("java.util.Base64" + s); }
第三种,使用安卓自带的 String s = android.util.Base64.encodeToString("好有钱" .getBytes(), 0 );System.out.println("android.util.Base64" + s);
Base64码表的妙用 为了传输数据安全,通常会对Base64数据进行URL编码,或者会把+和/替换成-和_
Base64编码细节 每个Base64字符代表原数据中的6bit
Base64编码后的字符数,是4的倍数
编码的字节数是3的倍数时,不需要填充
Base64编码的特点
Base64编码是编码,不是压缩,编码后只会增加字节数
算法可逆,解码很方便,不用于私密信息通讯
标准的Base64每行为76个字符,行末添加换行符
加密后的字符串只有65种字符,不可打印字符也可传输
在Java层可以通过hook对应方法名来快速定位关键代码
在so层可以通过输入输出的数据和码表来确定算法
MD5(消息摘要算法) 摘要长度为128bit
MD5的java实现 MessageDigest md5 = MessageDigest.getInstance("MD5" );md5.update("hyq" .getBytes()); md5.digest()
MD5的一些性质 加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
碰到加salt的MD5,可以直接输入空的值,得到结果去CMD5查询一下,有可能就得到salt(但是salt不能太长)
SHA(消息摘要算法) SHA是一系列算法,以SHA-1、SHA-256、、、命名
除了SHA-1的摘要长度是160bit,别的摘要长度都是后面的数字
SHA的Java实现 MessageDisgest sha1 = MessageDigest.getInstance("SHA-1" );sha1.update("hyq" .getBytes()); sha1.digest();
SHA的一些性质 加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
MAC系列算法(消息认证码,对称)
算法
摘要长度(bit)
HmacMD5
128
HmacSHA1
160
HmacSHA256
256
HmacSHA384
384
HmacSHA512
512
HmacMD2
128
HmacMD4
128
HmacSHA224
224
MAC算法与MD和SHA的区别是多了一个密钥,密钥可以随便给
MAC的java实现 SecretKeySpec secretKeySpec = new SecretKeySpec ("a12345678" .getBytes(), "HmacSHA1" );Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());mac.init(secretKeySpec); mac.update("hyq" .getBytes()); mac.doFinla();
MAC的一些性质 加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
DES(对称) DES的java实现 DESKeySpec desKeySpec = new DESKeySpce ("12345678" .getBytes());SecretKeyFactory key = SecretKeyFactory.getInstance("DES" );SecretKey secretKey = key.generateSecret(desKeySpec);SecretKeySpec secretKeySpec = new SecretKeySpec ("12345678" .getBytes(), "DES" );Cipher des = Cipher.getInstance("DES" );des.init(Ciphter.ENCRYPT_MODE, secretKeySpec); des.doFinal("hyq" .getBytes());
ECB模式和CBC模式的区别
没有指明加密模式和填充方式,表示使用默认的DES/ECB/PKCS5Padding
加密后的字节数组可以编码成Hex、Base64
DES算法明文按64位进行分组加密
要复现一个对称加密算法,需要得到以下几个东西
明文、key、iv、mode、padding
明文、key、iv需要注意解析方式,而且不一定是字符串形式
如果加密模式是ECB,则不需要iv
如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会
加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protubuf
DESede(对称) 用第一个密钥加密,然后用第二个密钥解密,最后再用第三个密钥加密
DESede的java实现 DESedeKeySpec desedeKey = new DESedeKeySpec ("1234567812345678" ).getBytes();SecretKeyFactory key = SecretKeyFactory.getInstance("DESede" );SecretKeySpec secretKey = key.generateSecret(desKeySpec);SecretKeySpec secretKeySpec = new SecretKeySpec ("1234567812345678" .getBytes(), "DESede" );Ciphter ciphter_desede = Cipher.getInstance("DESede" );ciphter_desede.init(Ciphter.ENCRYPT_MODE, secretKeySpec); ciphter_desede.doFinal("hyq" .getBytes());
AES(对称) 根据密钥长度的不同,可以分为AES128、AES192、AES256
AES的java实现 SecretKeySpec key = new SecretKeySpec ("1234567890abcdef" .getBytes(), "AES" );IvParamterSpec iv = new IvParamterSpec ("1234567890abcdef" .getBytes());Ciphter aes = Cipher.getInstance("AES/CBC/PKCS5Padding" );aes.init(1 , key, iv); aes.doFinal("a12345678" .getBytes())
ECB模式和CBC模式的区别 在对称加密算法里,如果使用NOPadding,加密的明文必须刚好等于分组长度倍数,否则会报错
如果使用PKCS5Padding,会对加密的明文填充1字节-1个分组的长度
RSA_Base64(非对称) 私钥的格式 pkcs1格式通常开头是 ——BEGIN RSA PRIVATE KEY——
pkcs8格式通常开头是 ——BEGIN PRIVATE KEY——
Java中的私钥必须是pkcs8格式
RSA密钥的解析 byte [] keyBytes = Base64Decoder.decodeBuffer(key);X509EncodeKeySpec keySpec = new X509EncodedKeySPec (keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA" );PublicKey publicKey = keyFactory.generatePublic(keySpec);byte [] keyBytes = Base64Decoder.decodeBuffer(key);PKCS8EncodeKeySpec keySpec = new PKCS8EncodedKeySpec (keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA" );PrivateKey privateKey = keyFactory.generetePrivate(keySpec);
RSA加解密 Cipher cipher = Cipher.getInstance("RSA/None/NoPadding" );cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte [] bt_encrypted = cipher.doFinal(bt_plaintext);Cipher cipher = Cipher.getInstance("RSA/None/NoPadding" );cipher.init(Cipher.DECRYPT_MODE, privateKey); byte [] bt_orinial = cipher.doFinal(bt_encrypted);
RSA模式和填充细节
RSA_Hex RSA密钥的解析 BigInteger N = new BigInteger (stringN, 16 );BigInteger E = new BigInteger (stringE, 16 );RSAPublicKeySpec spec = new RSAPublicKeySpec ("RSA" );PublicKey publicKey = keyFactory.getInstance("RSA" );PublicKey publicKey = keyFactory.generatePublic(spec);
多种加密算法的常见结合套路 随机生成AES密钥A
A密钥用于AES加密数据,得到数据密文B
使用RSA对A密钥加密,得到密钥密文C
提交密钥密文C和数据密文B给服务器
MainActivity.java package com.example.demo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.widget.TextView;import com.example.demo.databinding.ActivityMainBinding;import java.util.Base64;import java.util.Random;import okio.ByteString;public class MainActivity extends AppCompatActivity { static { System.loadLibrary("demo" ); } private ActivityMainBinding binding; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); try { String AESKey = generateAESKey(); String cipherText = AES.encryptAES("hyq" , AESKey); String cipherKey = RSA_Base64.encryptByPublicKey(AESKey); String plainKey = RSA_Base64.decryptByPrivateKey(cipherKey); String plainText = AES.decryptAES(ByteString.decodeBase64(cipherText).toByteArray(), plainKey); }catch (Exception e){ e.printStackTrace(); } } public native String stringFromJNI () ; public static String generateAESKey () { StringBuffer stringBuffer = new StringBuffer (); Random random = new Random (); for (int i = 0 ; i < 16 ; i++){ int random_i = random.nextInt(100 ); String temp = "0" + Integer.toHexString(random_i); temp = temp.substring(temp.length() - 2 ); stringBuffer.append(temp); } String resultKey = stringBuffer.toString(); return resultKey; } }
AES.java package com.example.demo;import java.security.InvalidAlgorithmParameterException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import javax.crypto.SecretKey;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import okio.ByteString;public class AES { public static String encryptAES (String plainText, String AESKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec aesKey = new SecretKeySpec (ByteString.decodeHex(AESKey).toByteArray(), "AES" ); IvParameterSpec ivParameterSpec = new IvParameterSpec ("0123456789abcdef" .getBytes()); Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding" ); aes.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec); byte [] bytes = aes.doFinal(plainText.getBytes()); ByteString of = ByteString.of(bytes); return of.base64(); } public static String decryptAES (String plainText, String AESKey) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec aesKey = new SecretKeySpec (ByteString.decodeHex(AESKey).toByteArray(), "AES" ); IvParameterSpec ivParameterSpec = new IvParameterSpec ("0123456789abcdef" .getBytes()); Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding" ); aes.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec); byte [] bytes = aes.doFinal(plainText.getBytes()); return new String (bytes); } }
RSA_Base64.java package com.example.demo;import java.security.KeyFactory;import java.security.PrivateKey;import java.security.Provider;import java.security.PublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.Cipher;import okio.ByteString;public class RSA_Base64 { public static String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxRQHxL/8xZ1EaNmQBGZnpMiCY" + "7gRzog6nDjfBJacytEiVJnJRuq1V/D+JKaXDwetsCnSUaz65LCFHU09OSEYee5oC" + "iI0ql21EA306c91oT/fQpPngQGZHLUtDOUdJVlAKnicCvmR24NqyNKFuY8L0cnB1" + "zcax73Rf+Ctf/lxAOwIDAQAB" ; public static String privateKeyBase64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPFFAfEv/zFnURo2\n" + "ZAEZmekyIJjuBHOiDqcON8ElpzK0SJUmclG6rVX8P4kppcPB62wKdJRrPrksIUdT\n" + "T05IRh57mgKIjSqXbUQDfTpz3WhP99Ck+eBAZkctS0M5R0lWUAqeJwK+ZHbg2rI0\n" + "oW5jwvRycHXNxrHvdF/4K1/+XEA7AgMBAAECgYEAsGkDrYWps0bW7zKb1o4Qkojb\n" + "etZ2HNJ+ojlsHObaJOHbPGs7JXU4bmmdTz5LfSIacAoJCciMuTqCLrPEhfmkghPq\n" + "U2MjyjfqYdXALoP7l/vt6QmjY/g1IAsaZN9nFhyjJ2WzgOx1f7gZj4NBSvTdSj7H\n" + "m5E24zkm+p7Qw1z6/mkCQQD7WSXAXcv2v3Vo6qi1FUlkzQgCQLFYqXNSOSPpno3y\n" + "oohUFIkMj0bYGbVE1LzV30Rb6Z8e8yQAByw6l8RuGb2PAkEA9bwb2euyOe6CcqpE\n" + "PNFc+7UlOJAy5epVFKHbu0aNivVpU0hsphqjIGXJGHYTspyEOLqtzILqKPZr6pru\n" + "WvJUlQJBAJoImQUZtlyCGs7wN/G5mN/ocscGpGikd+Lk16hdHbqbdpaoexCyYYUf\n" + "xCHpicw75mW5d2V9Ngu6WZWS2rNqnOsCQCoMK//X8sEy7KNOOyrk8DIpxtqs4eix\n" + "dil3oK+k3OdgIsubYuvxNuR+RjCnU6uGWKGUX9TUudiUgda89/gb6xkCQFm8gD6n\n" + "AyN+PPPKRq2M84+cAbnvjdIAY3OFHfkaoWCtEj5DR0UDuVv7jN7+re2D7id/GkAe\n" + "FAmhvYQwwLnifrw=" ; public static PublicKey generatePublicKey () throws Exception{ byte [] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray(); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec (publicKeyBase64Bytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); return keyFactory.generatePublic(x509EncodedKeySpec); } public static PrivateKey generatePrivateKey () throws Exception{ byte [] privateKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray(); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec (privateKeyBase64Bytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); return keyFactory.generatePrivate(pkcs8EncodedKeySpec); } public static String encryptByPublicKey (String plainText) throws Exception{ PublicKey publicKey = generatePublicKey(); Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding" ); instance.init(Cipher.ENCRYPT_MODE, publicKey); byte [] bytes = instance.doFinal(plainText.getBytes()); ByteString of = ByteString.of(bytes); return of.base64(); } public static String decryptByPrivateKey (String cipherText) throws Exception{ PrivateKey privateKey = generatePrivateKey(); Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding" ); instance.init(Cipher.DECRYPT_MODE, privateKey); byte [] bytes = instance.doFinal(cipherText.getBytes()); return new String (bytes); } }
数字签名算法 签名 PrivateKey priK = getPrivateKey(str_priK);Signature sig = Signature.getInstance("SHA256withRSA" );sig.initSign(priK); sig.update(data); sig.sign();
验证 PublicKey pubK = getPublicKey(str_pubK);Signature sig = Signature.getInstance("SHA256withRSA" );sig.initVerify(pubK); sig.update(data); sig.verify(sign);
package com.example.fctf;import android.os.Bundle;import android.util.Base64;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import androidx.activity.EdgeToEdge;import androidx.appcompat.app.AppCompatActivity;import androidx.core.graphics.Insets;import androidx.core.view.ViewCompat;import androidx.core.view.WindowInsetsCompat;import com.example.fctf.databinding.ActivityMainBinding;import org.w3c.dom.Text;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.security.MessageDigest;import java.security.spec.KeySpec;import java.util.Objects;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;import javax.crypto.spec.IvParameterSpec;import okio.ByteString;public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; private int num = 1 ; private static String key0 = "82305002" ; private static String iv0 = "82505002" ; public final int getNum () { return this .num; } public void setNum (int i) { this .num = i; } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); final TextView textView = binding.textView; Button button = binding.button; button.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { try { MainActivity.a(MainActivity.this , textView, button); } catch (IllegalBlockSizeException e) { throw new RuntimeException (e); } catch (BadPaddingException e) { throw new RuntimeException (e); } } }); try { System.out.println(decrypt("whyysqwmstoryhzcontinues" )); } catch (IllegalBlockSizeException e) { throw new RuntimeException (e); } catch (BadPaddingException e) { throw new RuntimeException (e); } } public static final void a (MainActivity this $0 , TextView tv, Button button) throws IllegalBlockSizeException, BadPaddingException { MainActivity mainActivity = this $0 ; tv.setText(String.valueOf(this $0. num)); if (this $0. check() == 20220422 ){ Toast.makeText(mainActivity, "Congratuations!!!" , Toast.LENGTH_SHORT).show(); tv.setText("flag{" + this $0. decrypt("whyysqwmstoryhzcontinues" ) + "}" ); } } public final int check () { int i = this .num + 1 ; this .num = i; return i; } public final String decrypt (String str) throws IllegalBlockSizeException, BadPaddingException { Cipher des = null ; byte [] bytes =null ; try { Class<?> a = Class.forName(d("amF2YS5zZWN1cml0eS5NZXNzYWdlRGlnZXN0" )); Method b = a.getMethod(d("Z2V0SW5zdGFuY2U=" ), String.class); Object c = b.invoke(null , d("TUQ1" )); Method e = a.getMethod(d("dXBkYXRl" ), byte [].class); e.invoke(c, key0.getBytes()); Method f = a.getMethod(d("ZGlnZXN0" )); byte [] k = (byte []) f.invoke(c); Class<?> desKeySpecClass = Class.forName("javax.crypto.spec.DESKeySpec" ); Constructor<?> desKeySpecConstructor = desKeySpecClass.getConstructor(byte [].class); KeySpec dsk = (KeySpec) desKeySpecConstructor.newInstance(k); Class<?> secretKeyFactoryClass = Class.forName("javax.crypto.SecretKeyFactory" ); Method getInstanceMethod = secretKeyFactoryClass.getMethod("getInstance" , String.class); SecretKeyFactory keyFactory = (SecretKeyFactory) getInstanceMethod.invoke(null , "DES" ); Method generateSecretMethod = secretKeyFactoryClass.getMethod("generateSecret" , KeySpec.class); SecretKey key = (SecretKey) generateSecretMethod.invoke(keyFactory, dsk); Class<?> ivParameterSpecClass = Class.forName("javax.crypto.spec.IvParameterSpec" ); Constructor<?> ivParameterSpecConstructor = ivParameterSpecClass.getConstructor(byte [].class); IvParameterSpec iv = (IvParameterSpec) ivParameterSpecConstructor.newInstance(iv0.getBytes()); Class<?> cipherClass = Class.forName("javax.crypto.Cipher" ); Method getInstanceCipherMethod = cipherClass.getMethod("getInstance" , String.class); des = (Cipher) getInstanceCipherMethod.invoke(null , "DES/CBC/PKCS5Padding" ); Method initMethod = cipherClass.getMethod("init" , int .class, java.security.Key.class, java.security.spec.AlgorithmParameterSpec.class); initMethod.invoke(des, Cipher.ENCRYPT_MODE, key, iv); Method doFinalMethod = cipherClass.getMethod("doFinal" , byte [].class); bytes = (byte []) doFinalMethod.invoke(des, str.getBytes()); } catch (Exception e) { e.printStackTrace(); } ByteString byteString = ByteString.of(bytes); return byteString.hex(); } private String d (String str) { return new String (Base64.decode(str, 0 )); } }