security: Đóng gói và bảo vệ khóa giải mã nội bộ trong binary, gỡ bỏ hoàn toàn khỏi cấu hình và tài liệu
This commit is contained in:
@@ -42,7 +42,6 @@
|
||||
"current_version": "v1.0.2"
|
||||
},
|
||||
"license": {
|
||||
"crypto_key": "ENC(R8h55AZzKMVH6QO+X/mBHvoMBLSBOZ9HCXSH2CbLrEwCUgGzFf7aJ5p8VCv7DZAMY6TkJQemYZIcXRS0)",
|
||||
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,6 @@ type UpdaterConfig struct {
|
||||
}
|
||||
|
||||
type LicenseConfig struct {
|
||||
CryptoKey string `json:"crypto_key"` // stored as ENC(...)
|
||||
Key string `json:"key"` // stored ciphertext U2FsdGVk...
|
||||
}
|
||||
|
||||
@@ -61,7 +60,6 @@ type AppConfig struct {
|
||||
DecryptedDBPassword string `json:"-"`
|
||||
DecryptedUpdaterToken string `json:"-"`
|
||||
DecryptedAdminPassword string `json:"-"`
|
||||
DecryptedCryptoKey string `json:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -162,7 +160,6 @@ func DefaultConfig() *AppConfig {
|
||||
CurrentVersion: "v1.0.2",
|
||||
},
|
||||
License: LicenseConfig{
|
||||
CryptoKey: "BAD5A05F330D5B8CDDFCA9E7ECDC840B",
|
||||
Key: "",
|
||||
},
|
||||
}
|
||||
@@ -182,18 +179,15 @@ func LoadConfig(path string) (*AppConfig, error) {
|
||||
cfg.DecryptedAdminPassword = cfg.Server.AdminPassword
|
||||
cfg.DecryptedDBPassword = cfg.Database.Password
|
||||
cfg.DecryptedUpdaterToken = cfg.Updater.Token
|
||||
cfg.DecryptedCryptoKey = cfg.License.CryptoKey
|
||||
|
||||
// Encrypt for disk save
|
||||
encAdmin, _ := Encrypt(cfg.Server.AdminPassword)
|
||||
encDB, _ := Encrypt(cfg.Database.Password)
|
||||
encTok, _ := Encrypt(cfg.Updater.Token)
|
||||
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
|
||||
|
||||
cfg.Server.AdminPassword = encAdmin
|
||||
cfg.Database.Password = encDB
|
||||
cfg.Updater.Token = encTok
|
||||
cfg.License.CryptoKey = encCrypto
|
||||
|
||||
data, _ := json.MarshalIndent(cfg, "", " ")
|
||||
_ = os.MkdirAll(filepath.Dir(configPath), 0755)
|
||||
@@ -217,24 +211,10 @@ func LoadConfig(path string) (*AppConfig, error) {
|
||||
decAdmin, _ := Decrypt(cfg.Server.AdminPassword)
|
||||
decDB, _ := Decrypt(cfg.Database.Password)
|
||||
decTok, _ := Decrypt(cfg.Updater.Token)
|
||||
if cfg.License.CryptoKey == "" {
|
||||
cfg.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
}
|
||||
decCrypto, _ := Decrypt(cfg.License.CryptoKey)
|
||||
if decCrypto == "" {
|
||||
decCrypto = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
}
|
||||
|
||||
cfg.DecryptedAdminPassword = decAdmin
|
||||
cfg.DecryptedDBPassword = decDB
|
||||
cfg.DecryptedUpdaterToken = decTok
|
||||
cfg.DecryptedCryptoKey = decCrypto
|
||||
|
||||
// Ensure crypto key is stored encrypted on disk
|
||||
if !strings.HasPrefix(cfg.License.CryptoKey, "ENC(") {
|
||||
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
|
||||
cfg.License.CryptoKey = encCrypto
|
||||
}
|
||||
|
||||
// Auto-migrate legacy owner & repo
|
||||
if cfg.Updater.Owner == "" || cfg.Updater.Owner == "hdhiswork" {
|
||||
@@ -270,14 +250,6 @@ func SaveConfig(cfg *AppConfig) error {
|
||||
enc, _ := Encrypt(clone.Updater.Token)
|
||||
clone.Updater.Token = enc
|
||||
}
|
||||
if clone.License.CryptoKey == "" {
|
||||
clone.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
}
|
||||
if !strings.HasPrefix(clone.License.CryptoKey, "ENC(") {
|
||||
clone.DecryptedCryptoKey = clone.License.CryptoKey
|
||||
enc, _ := Encrypt(clone.License.CryptoKey)
|
||||
clone.License.CryptoKey = enc
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(&clone, "", " ")
|
||||
if err != nil {
|
||||
|
||||
@@ -106,14 +106,13 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
|
||||
}
|
||||
],
|
||||
"license": {
|
||||
"crypto_key": "ENC(U2FsdGVkX18...)",
|
||||
"active_key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> - Mật khẩu CSDL (`password`) và Passphrase giải mã (`crypto_key`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn.
|
||||
> - Mật khẩu CSDL (`password`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn.
|
||||
> - Có thể thêm bao nhiêu CSDL tùy ý vào mảng `databases`. Người dùng có thể chuyển đổi linh hoạt ngay trên dropdown của thanh công cụ Web.
|
||||
|
||||
---
|
||||
@@ -123,7 +122,7 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
|
||||
### 5.1 Quy cách và Cấu trúc Bản quyền
|
||||
Hệ thống sử dụng cơ chế bảo mật theo tiêu chuẩn thư viện `OTH.Adapter / HisPrivateCode`:
|
||||
- **Thuật toán**: OpenSSL EVP_BytesToKey (MD5 key derivation + AES-256-CBC).
|
||||
- **Khóa dẫn xuất nội bộ**: `BAD5A05F330D5B8CDDFCA9E7ECDC840B`.
|
||||
- **Cơ chế xác thực**: Khóa bí mật nội bộ được đóng gói và tích hợp tự động bên trong file thực thi binary/exe; khi triển khai người dùng hoặc kỹ thuật viên chỉ cần nhập duy nhất chuỗi token DECODE.
|
||||
- **Cấu trúc dữ liệu JSON sau giải mã**:
|
||||
```json
|
||||
{
|
||||
|
||||
+1
-12
@@ -30,9 +30,6 @@ func HandleGetAdminConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if respCfg.Updater.Token != "" {
|
||||
respCfg.Updater.Token = "••••••••"
|
||||
}
|
||||
if respCfg.License.CryptoKey != "" {
|
||||
respCfg.License.CryptoKey = "••••••••"
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
@@ -83,15 +80,7 @@ func HandleSaveAdminConfig(w http.ResponseWriter, r *http.Request) {
|
||||
input.Updater.Token = enc
|
||||
}
|
||||
|
||||
// Handle License Crypto Key
|
||||
if input.License.CryptoKey == "" || input.License.CryptoKey == "••••••••" {
|
||||
input.License.CryptoKey = current.License.CryptoKey
|
||||
input.DecryptedCryptoKey = current.DecryptedCryptoKey
|
||||
} else {
|
||||
input.DecryptedCryptoKey = input.License.CryptoKey
|
||||
enc, _ := config.Encrypt(input.License.CryptoKey)
|
||||
input.License.CryptoKey = enc
|
||||
}
|
||||
// Handle License Key
|
||||
if input.License.Key == "" {
|
||||
input.License.Key = current.License.Key
|
||||
}
|
||||
|
||||
+14
-11
@@ -114,13 +114,22 @@ func OpenSSLDecrypt(encryptedBase64 string, passphrase string) (string, error) {
|
||||
return string(plaintext[:len(plaintext)-padLen]), nil
|
||||
}
|
||||
|
||||
// internalSecretKey returns the internal private passphrase (DHCryptoJSKEY)
|
||||
func internalSecretKey() string {
|
||||
part1 := []byte{0x42, 0x41, 0x44, 0x35, 0x41, 0x30, 0x35, 0x46}
|
||||
part2 := []byte{0x33, 0x33, 0x30, 0x44, 0x35, 0x42, 0x38, 0x43}
|
||||
part3 := []byte{0x44, 0x44, 0x46, 0x43, 0x41, 0x39, 0x45, 0x37}
|
||||
part4 := []byte{0x45, 0x43, 0x44, 0x43, 0x38, 0x34, 0x30, 0x42}
|
||||
return string(append(append(append(part1, part2...), part3...), part4...))
|
||||
}
|
||||
|
||||
// ValidateAndParseLicense parses and evaluates the license ciphertext
|
||||
func ValidateAndParseLicense(encryptedKey string, cryptoKey string) (*LicenseInfo, error) {
|
||||
if strings.TrimSpace(encryptedKey) == "" {
|
||||
return nil, errors.New("chuỗi key bản quyền trống")
|
||||
}
|
||||
if strings.TrimSpace(cryptoKey) == "" {
|
||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
cryptoKey = internalSecretKey()
|
||||
}
|
||||
|
||||
decryptedJSON, err := OpenSSLDecrypt(encryptedKey, cryptoKey)
|
||||
@@ -181,11 +190,6 @@ func InitLicense() {
|
||||
return
|
||||
}
|
||||
|
||||
cryptoKey := cfg.DecryptedCryptoKey
|
||||
if cryptoKey == "" {
|
||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
}
|
||||
|
||||
if cfg.License.Key == "" {
|
||||
currentLicense = &LicenseInfo{
|
||||
IsValid: false,
|
||||
@@ -195,7 +199,7 @@ func InitLicense() {
|
||||
return
|
||||
}
|
||||
|
||||
info, err := ValidateAndParseLicense(cfg.License.Key, cryptoKey)
|
||||
info, err := ValidateAndParseLicense(cfg.License.Key, "")
|
||||
if err != nil {
|
||||
currentLicense = &LicenseInfo{
|
||||
IsValid: false,
|
||||
@@ -231,12 +235,11 @@ func GetStatus() *LicenseInfo {
|
||||
// Activate saves and applies a new license key
|
||||
func Activate(encryptedKey string) (*LicenseInfo, error) {
|
||||
cfg := config.Get()
|
||||
cryptoKey := cfg.DecryptedCryptoKey
|
||||
if cryptoKey == "" {
|
||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
if cfg == nil {
|
||||
return nil, errors.New("cấu hình chưa được khởi tạo")
|
||||
}
|
||||
|
||||
info, err := ValidateAndParseLicense(encryptedKey, cryptoKey)
|
||||
info, err := ValidateAndParseLicense(encryptedKey, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
func TestOpenSSLDecryptUserKey(t *testing.T) {
|
||||
cipherText := "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||
passphrase := "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
||||
|
||||
info, err := ValidateAndParseLicense(cipherText, passphrase)
|
||||
// Passing empty cryptoKey triggers internalSecretKey() automatically
|
||||
info, err := ValidateAndParseLicense(cipherText, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Validation failed: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user