---
# System prepended metadata

title: 綠界付款及電子發票資料加密with Golang
tags: [Golang, ' XinPJ', ' 金流']

---

---
tags: Golang, XinPJ, 金流
---

# 綠界付款及電子發票資料加密with Golang

## # 付款:encodeURL + SHA256
- 官方文件: [綠界科技全方位金流介接技術文件
](https://www.ecpay.com.tw/Content/files/ecpay_011.pdf)
- 處理方式: encodeURL + SHA256
### EncodeURL

1. 轉16進位`中ASCII碼是-10544  => 16進位 D6D0`
2. 右到左，四位一組，每兩位轉換成一個單位，並在前面加上% => %XY`%D6%D0`
3. 不同語言的ENCODEURL可能有部分不同結果，需要再處理成綠界要的結果
    

|Golang結果 | 綠界要求(.Net) |
| -------- | -------- |
| %21      | !     |
| ~        | %7e   |
| %2A      | *     |
| %28      | (     |
| %29      | )     |

```
//encode 完再用replace轉換
encodeStr := url.QueryEscape(str)

// 轉為.Net encode結果
encodeStr = strings.ReplaceAll(encodeStr, "%21", "!")
encodeStr = strings.ReplaceAll(encodeStr, "~", "%7e")
encodeStr = strings.ReplaceAll(encodeStr, "%2A", "*")
encodeStr = strings.ReplaceAll(encodeStr, "%28", "(")
encodeStr = strings.ReplaceAll(encodeStr, "%29", ")")
    
```

### SHA256
安全雜湊演算法（英語：Secure Hash Algorithm，縮寫為SHA）是一個密碼雜湊函式家族(SHA系列算法是一種密碼散列函數，由美國國家安全局設計，並由美國國家標準技術研究所（NIST）發布為聯邦數據處理標準（FIPS）)
* 特色: 輸出固定長度,不受原文長短影響，雜湊是單向，無法解回原文，用意在判斷檔案是否和原本相同。
1. 將資料按照字母A-Z順序排好
2. ENCODE加密
3. 轉為小寫
4. SHA加密
    STEP1：附加填充位元:原文末進行填充(先補第一個位元為1，然後都補0)，直到長度滿足對512取模後餘數是448。
    為什麼是448? 之後會附加一個64bit的資料，用來表示原始報文的長度資訊。而448 64=512，正好拼成了一個完整的結構。
    
5. 轉大寫後存為CheckMacValue
    [詳細介紹SHA256運作過程](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/602774/)
    ```
    hash := sha256.New()
	hash.Write([]byte(encodeStr))
	md := hash.Sum(nil)
	macValue := hex.EncodeToString(md)

	macValue = strings.ToUpper(macValue)

	e.CheckMacValue = macValue
    ```

## # 電子發票: encodeURL + AES
- 官方文件: [電子發票 B2C 介接技術文件](https://www.ecpay.com.tw/Content/files/ecpay_004.pdf)

- 處理方式: data須為加密的JSON，加密方式: encodeURL + AES(128 bit，CipherMode : CBC， PaddingMode:PKCS7)，回傳的資料一同方式解密。

### AES 
高階加密標準(AES,Advanced Encryption Standard)為最常見的**對稱加密演算法**，透過對每個固定大小的4x4位元矩陣區塊(block = 128 bits = 16 bytes)，對其每一個元素進行多次交互置換和XOR運算
- 對稱加密: 加解密鑰匙相同
鑰匙+原文 ==通過AES函式==> 加密文
 
- AES的基本結構
    - 分組加密: 把原文分成一組一組，每組長度相等，每次加密一組資料
    - secret key長度有128 bits, 192 bits和256 bits
    - 128 bit: 金鑰長度（32位位元字):4/分組長度(32位位元字):4/加密輪數:10
    
```
P = abcdefghijklmnop,其中的字元a對應P0，p對應P15
最後一輪迭代不執行列混合。
```

- 加密流程
    - AddRoundKey
  ![](https://i.imgur.com/00EUMAl.png)


    - SubBytes
        use Rijndael S-box，每一個元素通過 S-box 進行代換，有S盒和逆S盒。把該位元組的高4位作為行值，低4位作為列值，取出S盒或者逆S盒中對應的行的元素作為輸出
Ref. [AES加密演算法的詳細介紹與實現](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/542873/)

    - ShiftRows: 每一行都進行一個向左循環移位，每一列上的元素都是非線性
     ![](https://i.imgur.com/pNjw9AT.png)


    - MixColumns:輸入矩陣的每一列與一個固定的多項式在一定條件下相乘。最終得到的將會是一個與輸入矩陣完全不一樣的輸出矩陣
![](https://i.imgur.com/Hn3a4kU.png)



- 循環: 循環次數由密鑰的長度決定
    - 首次循環：

        **AddRoundKey**

    - 一般循環：

        SubBytes

        ShiftRows

        **MixColumns**

        AddRoundKey

    - 末尾循環：

        SubBytes

        ShiftRows

        AddRoundKey

### CBC (Cipher-block chaining)
CBC是一種串鏈的加密方式，第一個資料區塊必須加入IV和金鑰(secret key)進行加密。
- 需要IV & Key
- 下一個區塊必須依賴前一個區塊加密後的結果才能夠得到自己要使用的IV，故每一區塊要依序加密(不能同時進行)
![](https://i.imgur.com/EJdPOwm.png)


###  搭配AES的填充的方法: PKCS7
對於塊加密算(AES)來說，如果數據的長度不滿一個塊的大小，則需要主動填充一些數據，讓這個塊的大小可以滿足要求。
> PKCS5 相當於是 PKCS7 的一個子集，因為 PKCS7 理論上支持1~255字節的塊大小填充，而 PKCS5 只支持8字節的塊大小填充。其實 PKCS5 更多是應用在 DES/3DES 上。

* PKCS7: 目標塊大小16
原文:
`FF FF FF FF FF FF FF FF FF（9個FF）`

    每一個字節都是填充長度的十六進位數(7)，填充結果:
`FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07（9個FF和7個07）`

* PKCS5: 目標塊大小8
`FF FF FF FF FF FF FF FF FF 01`

### Base64編碼
- Base64可以把所有的二進位數據都轉換成ASCII碼可列印字符串
- 將二進位數據進行分組，每24Bit(3字節)為一個大組，一個大組再以6Bit分成4個小組。
- 6Bit數據只能表示64個不同的字符(2^6=64)
- 加密後結果特徵: Base64字符串中會出現'+'和'\'兩種字符，字符串的末尾經常會有一個到兩個連續的'='。
- 使用Golang的AES加解密時，需要通過Base64

#### data轉換加密流程
```
// data物件轉為json
dataStr, _ := json.Marshal(formData)

// AES加密
encode, _ := encrypt.AesEncryptSimple([]byte(dataStr), key, iv)

// Base編碼
result := base64.StdEncoding.EncodeToString(encode)
```

#### 加密function

```
//  加密

func AesEncryptSimple(origData []byte, key string, iv string) ([]byte, error) {
	return AesEncryptPkcs5(origData, []byte(key), []byte(iv))
}

func AesEncryptPkcs5(origData []byte, key []byte, iv []byte) ([]byte, error) {
	return AesEncrypt(origData, key, iv, PKCS5Padding)
}

func AesEncrypt(origData []byte, key []byte, iv []byte, paddingFunc func([]byte, int) []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	blockSize := block.BlockSize()
	origData = paddingFunc(origData, blockSize)

	blockMode := cipher.NewCBCEncrypter(block, iv)
	crypted := make([]byte, len(origData))
	blockMode.CryptBlocks(crypted, origData)
	return crypted, nil
}

```

#### 解密流程

```
// 原文 base64解編碼
data, _ := base64.StdEncoding.DecodeString(str)

// AES 解密
aesDecrypt, _ := encrypt.AesDecryptSimple(data, key, iv)

// encodeURL 解編碼
decodeURI, err := url.QueryUnescape(string(aesDecrypt))
```


#### 解密function

```
//解密
func AesDecryptSimple(crypted []byte, key string, iv string) ([]byte, error) {
	return AesDecryptPkcs5(crypted, []byte(key), []byte(iv))
}

func AesDecryptPkcs5(crypted []byte, key []byte, iv []byte) ([]byte, error) {
	return AesDecrypt(crypted, key, iv, PKCS5UnPadding)
}

func AesDecrypt(crypted, key []byte, iv []byte, unPaddingFunc func([]byte) []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	blockMode := cipher.NewCBCDecrypter(block, iv)
	origData := make([]byte, len(crypted))
	blockMode.CryptBlocks(origData, crypted)
	origData = unPaddingFunc(origData)
	return origData, nil
}

```

```
//填充

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	if length < unpadding {
		return []byte("unpadding error")
	}
	return origData[:(length - unpadding)]
}

```




### 同場加映: 非對稱- 加解密鑰匙不同
商業上常遇到一對多的情形，對稱加密有多人人要使用，就要管理多少支鑰匙，為了解決交換時被竊取以及金鑰管理性問題，在密碼學中演化出一種公鑰加密演算法(public-key cryptography)，也就是1把公開金鑰給500個親朋好友用來加密，加密後的密文只能用私密金鑰解開(自己手上一把就行了)
```
私密金鑰解密(公開金鑰加密(明文))=明文 
by 1976年圖靈獎得主 Whitfield Diffe及Martin Hellman。
```




## 資安處理小知識補充

### 雜湊(SHA)vs加密

最大不同點
1. 加密需要密鑰
2. 加密可還原，雜湊不行

> pkcs7 簽章使用 RSA 加密演算法對資料的 SHA256 雜湊值簽章，台灣的金融機構習慣對這簽章做 base64 編碼來避免古早用 Cobol 的系統以 ASCII 字碼接收而產生所有資料第 8 bit 都是 0 而引起的驗證錯誤。

### 雜湊(Hash)
1. 特色
    - 輸出固定長度,不受原文長短影響)(數字256表示產生256bit長的雜湊值)
    - 不同雜湊演算法的輸出長度不同
    - 兩個原文的內容即便只差一個字，輸出內容卻會差非常多。
    - 不同的內容使用相同雜湊演算法，得到相同輸出的機率極低。(Hash Collision 的資安攻擊過)
    - 雜湊是單向，無法解回原文
2. 為何支付使用
    - 判斷檔案是否和原本相同
    - 不需要還原資料

3. SHA家族
- SHA-0
- SHA-1 : 不夠安全（在可接受的時間範圍內被找到不同原文但相同雜湊。）
- SHA-2
    - SHA-256 (綠界使用
    - SHA-512
- SHA-3
    - SHA3-256 
    - SHA3-512

其他: MD5(不夠安全)、BLAKE2

### 加密（Encrypt）
1. 特色
    - 需要密鑰 
    - 對稱式加密（Symmetric Encryption）: 僅一把鑰匙
        - DES, 3DES, AES
    - 非對稱式加密（Asymmetric Encryption）: 兩把鑰匙(公鑰可以公開，私鑰自己藏好）
        - RSA, DSA, ECC
    - 使用 base64 的時候不需要密鑰，所以 base64 不是加密。

### 壓縮（Compression）
- 定義 : 操作後讓輸出小於輸入
- 常見演算法: zip, gzip, rar, 7zip, jpg, mp3, mp4, Huffman coding
- 前端使用:網頁上用 base64 表示圖片可以加速，但base64不是壓縮
    - 圖片以 base64 字串呈現在網頁上的話，就不需要額外request去取得圖片。但因為 base64後檔案會變大，所以很多人會先把圖片 gzip 壓縮，再編碼成 base64 字串。

### 編碼 （Encoding）
- 定義: 將原文轉為另一種表達方式
- 常見演算法: base64, urlencode, hamming code
- 用途: 錯誤偵測（Error Detection）、錯誤校正（Error Correction）、以利傳輸

### 參考資料:
[加密和雜湊有什麼不一樣？](https://blog.m157q.tw/posts/2017/12/25/differences-between-encryption-and-hashing/)

[如何區分加密、壓縮、編碼](https://blog.m157q.tw/posts/2017/12/23/differences-between-encryption-compression-and-encoding/)

[AES加密演算法的詳細介紹與實現](https://kknews.cc/zh-tw/tech/5m5kpk.html)

[❲科普❳AES是個什麼鬼？](https://kknews.cc/tech/5m5kpk.html)

[Python M2Crypto - AES 的 Encrypt 與 Decrypt](http://ijecorp.blogspot.com/2013/08/python-m2crypto-aes-encrypt-decrypt.html)
