自己搞出來的冷門需求,自己解決。

先前分享過,遇到用環境變數儲存 API Key 的需求,正常人幾乎都是明碼存入了事,某人因資安偏執發作,硬要用 Windows DPAPI 加密才安心。當場景推到 Python,再度遇上從環境變數讀 API Key 的需求,明明已有加密版又另外明碼存一份感覺是個餿主意,於是我領到一個題目:如何在 Python 解密 DPAPI 加密內容?

爬文查到有個 Python for Win32 (pywin32) 擴充套件為 Python 提供 Win32 API 呼叫介面,其中包含 win32crypt.CryptUnprotectData,可用來解開 System.Security.Cryptography.ProtectedData 加密內容。

試了一下,先 pip install pywin32 裝好套件,只需幾行程式便能在 Python 端完成解密。

import os
import re
import win32crypt
import base64

def read_env_var(env_var):
    raw = os.environ.get(env_var)
    if not raw or os.name != "nt":
        return raw
    if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", raw):
        return raw
    try:
        val = base64.b64decode(raw)
        entropy = bytes([2, 8, 8, 2, 5, 2, 5, 2])
        dec = win32crypt.CryptUnprotectData(val, entropy, None, None, 0)
        return dec[1].decode("utf-8")
    except Exception:
        # return error message
        return "ERROR! " + str(Exception)

print("加密環境變數讀取結果 =", read_env_var("MY_API_KEY"))

後來想想,能不能不靠套件,直接從 Python 呼叫 PowerShell 解決呢?呼叫外部程式取得執行結果是高階程式語言的標配,Python 也不例外。Python 有個 subprocess.run 概念類似 .NET 用 Process 執行 EXE,簡單拼裝一下,用以下的程式也能順利解密。

import subprocess
import os

def read_env_var(env_var):
    if os.name != "nt":
        return os.environ.get(env_var)
    script = """
        Add-Type -AssemblyName System.Security
        try { 
            [Console]::OutputEncoding = [Text.Encoding]::UTF8; 
            $v=[Environment]::GetEnvironmentVariable('%s', [EnvironmentVariableTarget]::User); 
            $entropy = @(2, 8, 8, 2, 5, 2, 5, 2);
            [System.Text.Encoding]::UTF8.GetString([System.Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String($v),  $entropy, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)) 
        } 
        catch { 
            'ERROR! ' + $_.Exception.Message
        }
    """ % env_var
    res = subprocess.run(
        ["powershell", "-ExecutionPolicy", "Unrestricted", "-Command", script],
        capture_output=True, text=True, encoding="utf-8"
    )
    return res.stdout.strip()

print("加密環境變數讀取結果 =", read_env_var("MY_API_KEY"))

打完收工。

In this article, I demonstrate how to use pywin32 to decrypt content encrypted with System.Security.Cryptography.ProtectedData, as well as techniques for calling PowerShell scripts from Python.


Comments

Be the first to post a comment

Post a comment