C#如何使用Data Protection API加密数据

来源:站长联盟作者:上海SEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《C#如何使用Data Protection API加密数据》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#如何使用Data Protection API加密数据》有用,将其分享出去将是对创作者最好的鼓励。

在C#开发中,处理用户密码、接口密钥等敏感数据时,直接使用明文存储存在极大的安全风险,而自行实现加密算法又需要额外处理密钥管理的问题。Windows系统提供的Data Protection API(简称DPAPI)可以很好地解决这个问题,它内置在系统底层,会自动管理加密密钥,开发者只需要调用对应的接口就能完成数据的加密和解密操作,非常适合本地敏感数据的保护场景。

DPAPI的核心特性

DPAPI是Windows操作系统自带的加密服务,它的核心优势在于不需要开发者手动生成、存储和管理加密密钥,系统会自动使用当前用户的登录凭据或者计算机的唯一标识来生成加密密钥。DPAPI主要有两个作用范围:

  • 当前用户范围:加密的数据只有当前登录的用户可以解密,其他用户即使拿到加密后的数据也无法解密,适合存储用户个人的敏感配置信息。
  • 本地计算机范围:加密的数据可以在当前计算机上的所有用户账户下解密,适合存储应用程序级别的共享敏感数据。

C#中调用DPAPI的命名空间

C#中并没有直接封装DPAPI的托管类,需要调用Windows的原生API,我们可以通过平台调用(P/Invoke)的方式调用crypt32.dll中的两个核心函数:CryptProtectData用于加密数据,CryptUnprotectData用于解密数据。首先需要定义这两个函数的签名和相关的数据结构。

加密方法的实现

首先定义需要用到的结构体和函数签名,DATA_BLOB是存储二进制数据块的结构,CRYPTPROTECT_PROMPTSTRUCT是加密时的提示信息结构,实际使用中可以为空。

using System;
using System.Runtime.InteropServices;
using System.Text;

public class DpapiHelper
{
    // 定义DATA_BLOB结构体,用于存储二进制数据
    [StructLayout(LayoutKind.Sequential)]
    public struct DATA_BLOB
    {
        public int cbData;
        public IntPtr pbData;
    }

    // 定义加密提示结构体,实际使用时可以不需要额外设置
    [StructLayout(LayoutKind.Sequential)]
    public struct CRYPTPROTECT_PROMPTSTRUCT
    {
        public int cbSize;
        public int dwPromptFlags;
        public IntPtr hwndApp;
        public string szPrompt;
    }

    // 加密作用范围枚举
    public enum CryptProtectFlags
    {
        // 仅当前用户可解密
        CRYPTPROTECT_LOCAL_MACHINE = 0x04,
        // 本地计算机所有用户可解密,需要配合上面的标志使用
        // 不设置该标志则默认是当前用户范围
    }

    // 导入CryptProtectData函数,用于加密数据
    [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool CryptProtectData(
        ref DATA_BLOB pDataIn,
        string szDataDescr,
        ref DATA_BLOB pOptionalEntropy,
        IntPtr pvReserved,
        ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct,
        int dwFlags,
        ref DATA_BLOB pDataOut
    );

    // 导入CryptUnprotectData函数,用于解密数据
    [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool CryptUnprotectData(
        ref DATA_BLOB pDataIn,
        out string ppszDataDescr,
        ref DATA_BLOB pOptionalEntropy,
        IntPtr pvReserved,
        ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct,
        int dwFlags,
        ref DATA_BLOB pDataOut
    );

    // 导入LocalFree函数,释放非托管内存
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr LocalFree(IntPtr hMem);
}

接下来实现加密的公开方法,方法接收明文字符串和可选的作用范围参数,返回加密后的Base64字符串,方便存储和传输。

public static string Encrypt(string plainText, bool useLocalMachineScope = false)
{
    if (string.IsNullOrEmpty(plainText))
    {
        throw new ArgumentException("明文不能为空");
    }

    // 将明文字符串转换为字节数组
    byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
    IntPtr plainPtr = Marshal.AllocHGlobal(plainBytes.Length);
    Marshal.Copy(plainBytes, 0, plainPtr, plainBytes.Length);

    // 初始化输入数据块
    DATA_BLOB dataIn = new DATA_BLOB();
    dataIn.cbData = plainBytes.Length;
    dataIn.pbData = plainPtr;

    // 初始化输出数据块
    DATA_BLOB dataOut = new DATA_BLOB();
    dataOut.cbData = 0;
    dataOut.pbData = IntPtr.Zero;

    // 初始化提示结构
    CRYPTPROTECT_PROMPTSTRUCT promptStruct = new CRYPTPROTECT_PROMPTSTRUCT();
    promptStruct.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
    promptStruct.dwPromptFlags = 0;
    promptStruct.hwndApp = IntPtr.Zero;
    promptStruct.szPrompt = null;

    // 设置加密标志,默认是当前用户范围
    int flags = 0;
    if (useLocalMachineScope)
    {
        flags = (int)CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE;
    }

    try
    {
        // 调用加密函数
        bool result = CryptProtectData(
            ref dataIn,
            "DPAPI加密数据",  // 数据描述,可用于标识数据用途
            ref dataIn,  // 可选熵值,这里直接使用输入数据作为熵值,也可以自定义
            IntPtr.Zero,
            ref promptStruct,
            flags,
            ref dataOut
        );

        if (!result)
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Exception($"加密失败,错误码:{errorCode}");
        }

        // 将加密后的字节数组从非托管内存复制到托管数组
        byte[] encryptedBytes = new byte[dataOut.cbData];
        Marshal.Copy(dataOut.pbData, encryptedBytes, 0, dataOut.cbData);

        // 释放非托管内存
        LocalFree(dataOut.pbData);

        // 返回Base64格式的加密字符串
        return Convert.ToBase64String(encryptedBytes);
    }
    finally
    {
        // 释放明文字节的非托管内存
        Marshal.FreeHGlobal(plainPtr);
    }
}

解密方法的实现

解密方法接收加密后的Base64字符串,返回解密后的明文字符串,需要和加密时使用的范围和熵值匹配才能解密成功。

public static string Decrypt(string encryptedBase64)
{
    if (string.IsNullOrEmpty(encryptedBase64))
    {
        throw new ArgumentException("加密字符串不能为空");
    }

    // 将Base64字符串转换为字节数组
    byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64);
    IntPtr encryptedPtr = Marshal.AllocHGlobal(encryptedBytes.Length);
    Marshal.Copy(encryptedBytes, 0, encryptedPtr, encryptedBytes.Length);

    // 初始化输入数据块
    DATA_BLOB dataIn = new DATA_BLOB();
    dataIn.cbData = encryptedBytes.Length;
    dataIn.pbData = encryptedPtr;

    // 初始化输出数据块
    DATA_BLOB dataOut = new DATA_BLOB();
    dataOut.cbData = 0;
    dataOut.pbData = IntPtr.Zero;

    // 初始化提示结构
    CRYPTPROTECT_PROMPTSTRUCT promptStruct = new CRYPTPROTECT_PROMPTSTRUCT();
    promptStruct.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
    promptStruct.dwPromptFlags = 0;
    promptStruct.hwndApp = IntPtr.Zero;
    promptStruct.szPrompt = null;

    try
    {
        // 调用解密函数
        bool result = CryptUnprotectData(
            ref dataIn,
            out string dataDescr,
            ref dataIn,  // 熵值需要和加密时一致
            IntPtr.Zero,
            ref promptStruct,
            0,
            ref dataOut
        );

        if (!result)
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Exception($"解密失败,错误码:{errorCode}");
        }

        // 将解密后的字节数组从非托管内存复制到托管数组
        byte[] decryptedBytes = new byte[dataOut.cbData];
        Marshal.Copy(dataOut.pbData, decryptedBytes, 0, dataOut.cbData);

        // 释放非托管内存
        LocalFree(dataOut.pbData);

        // 将字节数组转换为明文字符串
        return Encoding.UTF8.GetString(decryptedBytes);
    }
    finally
    {
        // 释放加密字节的非托管内存
        Marshal.FreeHGlobal(encryptedPtr);
    }
}

使用示例

下面演示如何调用上面的加密和解密方法,分别测试当前用户范围和本地计算机范围的加密效果。

class Program
{
    static void Main(string[] args)
    {
        string sensitiveData = "my_secret_password_123";

        // 测试当前用户范围加密解密
        Console.WriteLine("当前用户范围加密测试:");
        string encrypted1 = DpapiHelper.Encrypt(sensitiveData, false);
        Console.WriteLine($"加密结果:{encrypted1}");
        string decrypted1 = DpapiHelper.Decrypt(encrypted1);
        Console.WriteLine($"解密结果:{decrypted1}");

        Console.WriteLine();

        // 测试本地计算机范围加密解密
        Console.WriteLine("本地计算机范围加密测试:");
        string encrypted2 = DpapiHelper.Encrypt(sensitiveData, true);
        Console.WriteLine($"加密结果:{encrypted2}");
        string decrypted2 = DpapiHelper.Decrypt(encrypted2);
        Console.WriteLine($"解密结果:{decrypted2}");
    }
}

注意事项

  • DPAPI是Windows系统特有的接口,所以上述代码只能在Windows环境下运行,无法在Linux或macOS系统中使用。
  • 当前用户范围的加密数据,只有加密时的登录用户才能解密,如果用户修改了登录密码或者切换了用户账户,之前加密的数据将无法解密。
  • 本地计算机范围的加密数据虽然可以被当前计算机的所有用户解密,但是不能在其他计算机上解密,因为密钥和计算机硬件信息绑定。
  • 如果需要在不同计算机之间共享加密数据,DPAPI并不适用,此时应该选择其他支持密钥交换的加密算法。

C#DPAPIData_Protection_API数据加密修改时间:2026-06-11 10:03:29

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。