###### tags: `📆2021年` `#💻編程/🛡資訊安全` `#💻編程/🌠Java/99_其他` `🗂Overview`
# 密碼學
- 密碼學是網路安全、信息安全、區塊鏈…學科的基礎
- 歷史:
- 古典密碼學:替換法、移位法(凱撒加密)
破解方法:頻率分析法(如英文中,e的出現的頻率最高)
- 近代密碼學:恩尼格瑪密碼機(核心也是使用移位法、替換法)-->被圖靈破解
- 現代密碼學:
1. 散列函數(哈希函數):常見加密方式,MD5、SHA-1、SHA-256
2. 對稱加密
- 流加密:[流加密 -wiki](https://zh.wikipedia.org/wiki/%E6%B5%81%E5%AF%86%E7%A0%81)
- 塊加密:[塊加密 -wiki](https://zh.wikipedia.org/zh-tw/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81)
3. 非對稱加密
## 🔐相關知識
### bit和byte的區別
- bit,位元,二進制(0或1),電腦運算基礎,一般以b表示
資料傳輸bps(bit per second):1Mbps = 1024 Kbps / 8 = 128 KB/s
```java
public static void main(final String srg[]){
final String str = "Hello";
System.out.println("print bit");
printBit(str.getBytes());
}
private static void printBit(final byte[] bytes) {
for (final byte b : bytes)
System.out.println(Integer.toBinaryString(b));
}
```
- byte,字節、位元組,電腦檔案大小基本單位,一般以B表示
1 B = 8 b
```java
public static void main(final String srg[]){
final String str = "Hello";
System.out.println("print byte");
printByte(str.getBytes());
}
private static void printByte(final byte[] bytes) {
for (final byte b : bytes)
System.out.println(b);
}
```
- 中文-->byte
1. UTF-8,一個中文 = 3 byte
2. GBK,一個中文 = 2 byte
```java
public static void main(final String srg[]) throws UnsupportedEncodingException {
final String str = "測";
System.out.println("print bit");
printBit(str.getBytes("GBK"));
System.out.println("print byte");
printByte(str.getBytes("GBK"));
}
private static void printBit(final byte[] bytes) {
for (final byte b : bytes)
System.out.println(Integer.toBinaryString(b));
}
private static void printByte(final byte[] bytes) {
for (final byte b : bytes)
System.out.println(b);
}
```
### base64
- base64不是加密算法,是可讀性算法(為了可讀性才計算)
- base64由64個字符組成,`a-z`、`A-Z`、`0-9`、`+`和`/`
- base58則由58個字符組成,一般用在比特幣裡的一種編碼方式,沒有`0`、`o`、`I`、`i`、`+`、`/`
- base64原理:base64是3個字節為一組,一共24位元,base64把三個字節轉換為4組,將值控制在0~63之間,如果不夠3個字節則以`=`補齊

### toString()和new String()區別
```java
public static void main(final String srg[]) throws Exception {
final String base64 = "01Jk6AecyqldguNoHIO7dw==";// 密文
final java.util.Base64.Decoder decoder = Base64.getDecoder();
final byte[] binary = decoder.decode(base64);
System.out.println("origin str= " + base64);
System.out.println("new String()= " + new String(binary));
System.out.println("toString()= " + binary.toString());
}
```
- `toString()`,實際上調用Object.toString(),若無特別覆寫則返回哈希值(類地址)
- `new String()`,根據指定編碼格式進行解碼,若無指定則用虛擬機的編碼格式;若是IOS-8859-1則到ASCII找相應字符
## 🔐ASCII編碼
字元對應表可參考:[ASCII-wiki](https://zh.wikipedia.org/wiki/ASCII)
```java
public static void main(final String args[]) {
final String str = "hello";
final char[] charArray = str.toCharArray();
System.out.println(charArray.hashCode());
for (final char temp : charArray) {
System.out.println(temp + "\t " + (int) temp);
}
}
```
## 🔐凱撒加密
字母移位
```java
public static void main(final String[] args) {
// param
final String origin = "Hello World";
final int key = -5;
// encryp
final String encryp = caesarEncryp(origin, key);
System.out.println("origin : " + origin);
System.out.println("encryp(" + key + ") : " + encryp);
// decryp
final int decrypKey = getCaesarDecrypLey(encryp);
final String decryp = caesarEncryp(encryp, decrypKey);
System.out.println("decryp(" + decrypKey + ") : " + decryp);
}
/**
* 凱撒加密
*
* @param origin 明文
* @param key 密鑰,位移位數
* @return 加密後字串
*/
private static String caesarEncryp(final String originStr, final int key) {
final char[] originChars = new String(originStr).toCharArray();
final StringBuilder sb = new StringBuilder();
for (final char c : originChars) {
sb.append(Character.toString((char) ((int) c + key)));
}
return sb.toString();
}
/**
* 頻率分析解密密鑰
* @param encrypStr 密文
* @return
*/
private static int getCaesarDecrypLey(final String encrypStr) {
// 輸入為空
if (encrypStr.length() == 0) {
return 0;
}
// 計算出現頻率最高的字母
final char[] encripChar = encrypStr.toLowerCase().toCharArray();
final Map<Character, Integer> countMap = new HashMap<Character, Integer>();
for (final char c : encripChar) {
final Integer before = countMap.get(c);
countMap.put(c, (before == null || before == 0) ? 1 : before + 1);
}
final char maxChar = countMap.entrySet()//
.stream()//
.max(Entry.comparingByValue()).get().getKey();
System.out.println("maxChar=" + maxChar + " ;count=" + countMap.get(maxChar));
// 計算與字母e的差距
return (int) 'e' - (int) maxChar;
}
```
## 🔐對稱加密 Symmetric Encryption
一組密鑰同時用作加密和解密

- 特點
1. 加密速度快
2. 密文不可逆
3. 若在編碼表上找不到對應字符會出現亂碼
4. 一般需要結合base64一起使用
- class Cipher,提供加解密密碼的功能,構成Java加密擴展(JCE)框架核心,參考:[Cipher -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html)
`Cipher cipher = Cipher.getInstance("算法/模式/填充");`,若無指定則為`"DES/ECB/PKCS5Padding"`
### 加解密算法
- DES(Data Encryption Standard),數據加密標準,是一種使用密鑰加密的塊算法
```java
public static void main(final String srg[]) throws Exception {
final String input = "Hello World";// 明文
final String key = "12345678";// 密鑰,DES必須為8個字節,AES必須為16字節
final String algrithm = "DES";// 加密算法
final String transformation = algrithm + "/ECB/PKCS5Padding";// 加密類型
final String encrypt = desEncrypt(input, key, transformation, algrithm);
System.out.println("EDS Encrypt= " + encrypt);
final String decrypt = desDecrypt(encrypt, key, transformation, algrithm);
System.out.println("EDS Decrypt= " + decrypt);
}
/**
* 使用DES方式加密
*
* @param input 明文
* @param key 密鑰
* @param transformation 加密類型
* @param algrithm 算法
* @return 密文
* @throws Exception
*/
private static String desEncrypt(final String input, final String key, final String transformation,
final String algrithm) throws Exception {
final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algrithm);// 加密規則
final Cipher cipher = Cipher.getInstance(transformation);// 加解密對象
cipher.init(Cipher.ENCRYPT_MODE, sks);
final byte[] outputBytes = cipher.doFinal(input.getBytes());//加解密操作
// 在編碼表找不到對應的字符,所以直接打印會出現亂碼
// System.out.println(new String(outputBytes));
// binary-->base64
final Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(outputBytes);
}
/**
* 使用DES方式解密
*
* @param input base64密文
* @param key 密鑰
* @param transformation 加密類型
* @param algrithm 算法
* @return 明文
* @throws Exception
*/
private static String desDecrypt(final String input, final String key, final String transformation,
final String algrithm) throws Exception {
// base64-->binary
final java.util.Base64.Decoder decoder = Base64.getDecoder();
final byte[] encodeBytes = decoder.decode(input);
final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algrithm);// 加密規則
final Cipher cipher = Cipher.getInstance(transformation);// 加解密對象
cipher.init(Cipher.DECRYPT_MODE, sks);
final byte[] outputBytes = cipher.doFinal(encodeBytes);//加解密操作
return new String(outputBytes);
}
```
- AES(Advanced Encryption Standard),采用塊口密算法,用來替代DES
做法和DES一樣,只是修改加密類型,而且密鑰必須為16個字節
### 加密模式
參考:[分組密碼工作模式 -wiki](https://zh.wikipedia.org/wiki/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F)
- ECB(Electronic Codebook):電子密碼本,明文分塊後使用==同一組密鑰==獨立加密

1. 特點:同時加密,一樣的原文會加密出一樣的密文
2. 優點:可并行處理數據,加解密速度快
3. 缺點:不能很好的保護數據
- CBC(Cipher-block Chaining):密碼塊連接,明文分塊後與前一個密文塊進行異或後,再進行加密。每個密文塊都依賴於它前面所有的明文塊

1. 優點:同樣的原文生成的密文不一樣
2. 缺點:串行處理數據,加解密速變快
3. 注意,使用需要另外新增IV參數
```java
/**
* 使用DES方式加密,CBC加密模式
*
* @param input 明文
* @param key 密鑰
* @return 密文
* @throws Exception
*/
private static String desEncrypt(final String input, final String key) throws Exception {
final IvParameterSpec iv = new IvParameterSpec("12345678".getBytes());// iv向量
final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), "DES");// 加密規則
final Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");// 加解密對象
cipher.init(Cipher.ENCRYPT_MODE, sks, iv);
final byte[] outputBytes = cipher.doFinal(input.getBytes());//加解密操作
final Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(outputBytes);
}
```
### 填充模式
當需要按塊處理的數據,數據長度不符合塊處理需求時,按照指定方法來填充塊的規則
- NoPadding,不填充
1. 在DES加密下,原文長度必須是8b的整數倍
2. 在AES加密下,原文長度必須是16b的整數倍
- PKCS5Padding,補足數據塊位數
## 🔐消息摘要算法 Message Digest
也稱散列函數、雜湊演函數(Hash function)
- 將長度不固定的信息(或稱消息)作為輸入參數,輸出固定長度的Hash值
- 為了保證輸入值,使用消息摘要計算出的值不可以篡改
- 特性:
1. 計算的資料來源無長度限制
2. 雜湊值長度永遠固定
3. 雜湊演算法為單向運算
4. 不同資料來源不會産出相同的雜湊值
- [在線雜湊産生器](https://tool.puckwang.com/tools/hash/),注意,輸出為16進制的字串
### Linux中進行雜湊
1. MD5,已破解
```console
[mickey@localhost ~]$ echo "hello"|openssl md5
(stdin)= b1946ac92492d2347c6235b4d2611184
```
1. SHA(Secure Hash Algorithm)安全散列算法
2. SHA1,已破解
```console
[mickey@localhost ~]$ echo "hello"|openssl sha1
(stdin)= f572d396fae9206628714fb2ce00f72e94f2258f
```
1. SHA224
```console
[mickey@localhost ~]$ echo "hello"|openssl sha224
(stdin)= 2d6d67d91d0badcdd06cbbba1fe11538a68a37ec9c2e26457ceff12b
```
1. SHA256
```console
[mickey@localhost ~]$ echo "hello"|openssl sha256
(stdin)= 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
```
1. SHA384
```console
[mickey@localhost ~]$ echo "hello"|openssl sha384
(stdin)= 1d0f284efe3edea4b9ca3bd514fa134b17eae361ccc7a1eefeff801b9bd6604e01f21f6bf249ef030599f0c218f2ba8c
```
1. SHA512
```console
[mickey@localhost ~]$ echo "hello"|openssl sha512
(stdin)= e7c22b994c59d9cf2b48e549b1e24666636045930d3da7c1acb299d1c3b7f931f94aae41edda2c2b207a36e10f8bcb8d45223e54878f5b316e7ce3b6bc019629
```
### Java8中進行雜湊
[MessageDigest -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html)
類型:MD5、SHA-1、SHA-224、SHA-256、SHA-384、SHA-512
```java
public static void main(final String[] args) throws Exception {
final String input = "Hello World";// 原文
final String type = "SHA";// 雜湊算法類型
final MessageDigest md = MessageDigest.getInstance(type);
final byte[] outputBinary = md.digest(input.getBytes());
// 將輸出binary轉為Base64
final String outputBase64 = Base64.getEncoder().encodeToString(outputBinary);
// 將輸出binary轉為16進制
final StringBuilder sb = new StringBuilder();
for (final byte b : outputBinary) {
final String hex = Integer.toHexString(b & 0xff);
sb.append(hex.length() == 1 ? "0" + hex : hex);// 補齊至兩位
}
System.out.println(String.format("original= %s;%s_base64= %s;%s_16= %s", input, type, outputBase64, type, sb));
}
```
## 🔐非對稱加密 Asymmetric Encryption

- 排對稱加密將金鑰分為公鑰及私鑰,公鑰可發布於網上,私鑰則用戶自行保管,因此會出現以下四種加解密情況:
1. 公鑰加密,公鑰解密-->此方法無意義,相當於無加密
2. 公鑰加密,私鑰解密
3. 私鑰加密,公鑰解密
4. 私鑰加密,私鑰解密
- Java相關類
1. [KeyPairGenerator -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyPairGenerator.html),密鑰對生成器
1. [KeyPair -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyPair.html),密鑰對
1. [PublicKey -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/PublicKey.html "interface in java.security")
2. [PrivateKey -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/PrivateKey.html "interface in java.security")
3. [KeyFactory -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyFactory.html),binery-->PublicKey/PrivateKey
4. [PKCS8EncodedKeySpec -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/spec/PKCS8EncodedKeySpec.html),私鑰規范
- 程式參考:
1. [Java加密與解密筆記(三) 非對稱加密](https://www.itread01.com/content/1508482697.html)
### RSA
[RSA加密 -wiki](https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95)
```java
public static void main(final String[] args) throws Exception {
final String algorithm = "RSA";// 密鑰對生成方式
final String input = "Hello World";//明文
System.out.println("origin= " + input);
// ! 第一步,取得公鑰和私鑰
genKey(algorithm);
// ! 第二步,使用公鑰或私鑰加密
final String ecryptStr = doEcrypt(algorithm, getKeyFromFile(algorithm, "D:\\private.pri"), input);
System.out.println("ecrypt= " + ecryptStr);
// ! 第三步,使用公鑰或私鑰解密
final String decrypt = doDecrypt(algorithm, getKeyFromFile(algorithm, "D:\\public.pub"), ecryptStr);
System.out.println("decrypt= " + decrypt);
}
/**
* 取得公鑰和私鑰
*
* @param algorithm 密鑰對生成方式
* @return 公私鑰
* @throws Exception
*/
private static void genKey(final String algorithm) throws Exception {
// 密鑰對生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm);
// 生成密鑰對
final KeyPair kp = kpg.generateKeyPair();
// 最後結果轉為base64存成文檔
saveKeyFile(kp.getPublic(), kp.getPrivate(), "D:\\public.pub", "D:\\private.pri");
}
/**
* 儲存公私鑰文檔
*
* @param keyMap
* @param publicKeyPath 公鑰文檔路徑
* @param privateKeyPath 私鑰文檔路徑
* @throws Exception
*/
private static void saveKeyFile(final PublicKey publicKey, final PrivateKey privateKey, final String publicKeyPath,
final String privateKeyPath) throws Exception {
final String encoding = "UTF-8";
final Encoder encoder = Base64.getEncoder();
FileUtils.write(new File(publicKeyPath), encoder.encodeToString(publicKey.getEncoded()), encoding);
FileUtils.write(new File(privateKeyPath), encoder.encodeToString(privateKey.getEncoded()), encoding);
}
/**
* 使用公鑰或私鑰加密
*
* @param algorithm 加密方式
* @param isPuclic 密鑰是否為公鑰
* @param keyFilePath 密鑰文檔路徑
* @param input 明文
* @return 密文
* @throws Exception
*/
private static String doEcrypt(final String algorithm, final Key key, final String input) throws Exception {
final Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
final byte[] outputBinary = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(outputBinary);
}
/**
* 使用公鑰或私鑰解密
*
* @param algorithm 加密方式
* @param key 公鑰或私鑰
* @param ecryptStr 密文
* @return 明文
* @throws Exception
*/
private static String doDecrypt(final String algorithm, final Key key, final String ecryptStr) throws Exception {
final byte[] decode = Base64.getDecoder().decode(ecryptStr);
final Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(decode));
}
/**
* 從檔案取得公鑰或私鑰
*
* @param algorithm 加密方式
* @param keyFilePath 公鑰或私鑰路徑
* @return 公鑰或私鑰
* @throws Exception
*/
private static Key getKeyFromFile(final String algorithm, final String keyFilePath) throws Exception {
final String encoding = "UTF-8";
// file --> String --> binery
final String keyString = FileUtils.readFileToString(new File(keyFilePath), encoding);
final byte[] keyBinery = Base64.getDecoder().decode(keyString);
// binery --> PublicKey/PrivateKey
final KeyFactory kf = KeyFactory.getInstance(algorithm);
Key key = null;// 密鑰
if (keyFilePath.endsWith(".pub")) {// 判斷公鑰或私鑰
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBinery);// 建立公鑰規則
key = kf.generatePublic(keySpec);
} else {
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBinery);// 建立私鑰規則
key = kf.generatePrivate(keySpec);
}
return key;
}
```
### ECC
`TODO`
### 數字簽名
- 定義:公鑰數字簽名,只有信息的發送者才能産生別人無法偽造的一段數字的字串。類似於紙本簽名
- 基本原理:

1. 發送者將文件進行雜湊以取得信息摘要(數字簽名),并將摘要附在上文件上
2. 發送者將帶摘要的文件用私鑰加密,并發送
3. 接收者用公鑰解密取得帶摘要的文件
4. 接收者對文件進行雜湊以取得的信息摘要和附在文件上的摘要比對,若一致代表此文件未被修改
- Java相關類
1. [Signature -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/Signature.html)
```java
public static void main(final String[] args) throws Exception {
final String algorithm = "RSA";// 密鑰對生成方式
final String algorithmForSign = "SHA1with" + algorithm;
final String input = "Hello World";//明文
System.out.println("origin= " + input);
// ! 第一步,取得公鑰
genKey(algorithm);
// ! 第二步,使用私鑰産生數字簽名
final String signStr = doSign(algorithm, algorithmForSign, input, "D:\\private.pri");
System.out.println("signStr= " + signStr);
// ! 第三步,將簽名和明文打包并用密鑰加密(一般非對稱加密,故先省略)
final String decyptStr = signStr + "_" + input;
// ! 第四步,使用公鑰驗證數字簽名
final boolean verifyResult = doVerifySign(algorithm, algorithmForSign, decyptStr, "D:\\public.pub");
System.out.println("verifyResult= " + verifyResult);
}
private static boolean doVerifySign(final String algorithm, final String algorithmForSign, final String decyptStr,
final String publicKeyPath) throws Exception {
// transfer signStr to binary
final String[] strs = decyptStr.split("_");
final byte[] signBinary = Base64.getDecoder().decode(strs[0]);
// get message binary for compare
final byte[] messageBinary = strs[1].getBytes();
// get public key
final Key key = getKeyFromFile(algorithm, publicKeyPath);
// do verify
final Signature signature = Signature.getInstance(algorithmForSign);
signature.initVerify((PublicKey) key);
signature.update(messageBinary);
return signature.verify(signBinary);
}
/**
* 取得公鑰和私鑰
*
* @param algorithm 密鑰對生成方式
* @return 公私鑰
* @throws Exception
*/
private static void genKey(final String algorithm) throws Exception {
// 密鑰對生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm);
// 生成密鑰對
final KeyPair kp = kpg.generateKeyPair();
// 最後結果轉為base64存成文檔
saveKeyFile(kp.getPublic(), kp.getPrivate(), "D:\\public.pub", "D:\\private.pri");
}
/**
* 儲存公私鑰文檔
*
* @param keyMap
* @param publicKeyPath 公鑰文檔路徑
* @param privateKeyPath 私鑰文檔路徑
* @throws Exception
*/
private static void saveKeyFile(final PublicKey publicKey, final PrivateKey privateKey, final String publicKeyPath,
final String privateKeyPath) throws Exception {
final String encoding = "UTF-8";
final Encoder encoder = Base64.getEncoder();
FileUtils.write(new File(publicKeyPath), encoder.encodeToString(publicKey.getEncoded()), encoding);
FileUtils.write(new File(privateKeyPath), encoder.encodeToString(privateKey.getEncoded()), encoding);
}
/**
* 使用私鑰産生數字簽名
*
* @param algorithm 密鑰對生成方式
* @param algorithmForSign 簽名生成方式
* @param input 明文
* @param privateKeyPath 密鑰路徑
* @return 電子簽名
* @throws Exception
*/
private static String doSign(final String algorithm, final String algorithmForSign, final String input,
final String privateKeyPath) throws Exception {
// get public key or private key
final Key key = getKeyFromFile(algorithm, privateKeyPath);
// do sign
final Signature signature = Signature.getInstance(algorithmForSign);
if (privateKeyPath.endsWith(".pub")) {
System.out.println("please use private key");
} else {
signature.initSign((PrivateKey) key);
}
signature.update(input.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 從檔案取得公鑰或私鑰
*
* @param algorithm 加密方式
* @param keyFilePath 公鑰或私鑰路徑
* @return 公鑰或私鑰
* @throws Exception
*/
private static Key getKeyFromFile(final String algorithm, final String keyFilePath) throws Exception {
final String encoding = "UTF-8";
// file --> String --> binery
final String keyString = FileUtils.readFileToString(new File(keyFilePath), encoding);
final byte[] keyBinery = Base64.getDecoder().decode(keyString);
// binery --> PublicKey/PrivateKey
final KeyFactory kf = KeyFactory.getInstance(algorithm);
Key key = null;// 密鑰
if (keyFilePath.endsWith(".pub")) {// 判斷公鑰或私鑰
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBinery);// 建立公鑰規則
key = kf.generatePublic(keySpec);
} else {
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBinery);// 建立私鑰規則
key = kf.generatePrivate(keySpec);
}
return key;
}
```
### 數字證書
- 數字證書相當於第三方身分認證,類似於雅思、托福證書,參考:
1. 整個流程可參考[[🌐網路架構_05_HTTPS協議]]
2. 參考:[數字證書圖解流程](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/723427/)
