How To: Use DPAPI to Encrypt and Decrypt Data
2012-12-07 13:23 呆河马 阅读(682) 评论(0) 编辑 收藏 举报The code below demonstrates how to call Data Protection API (DPAPI) functions
CryptProtectData and CryptUnprotectData to encrypt and decrypt data. The code
sample is provided in C#.
Introduction DPAPI functions encrypt and decrypt data using the Triple-DES algorithm. In addition to encryption and decryption, the API handles key generation and protection. DPAPI can generate two types of encryption keys: user- or machine-specific (these key types are commonly referred to as user store and machine store). User store and machine store are mutually exclusive; this means that you cannot combine a user-specific key with machine-specific key in one DPAPI call.
DPAPI with user store When making DPAPI calls with user-specific keys, encryption and decryption must be performed by the same user (i.e. the identity under which the application runs). Applications calling DPAPI functions with user-specific keys must run with loaded user profiles of Windows® domain or local accounts; they cannot use the profiles of the built-in system accounts, such as LocalSystem, ASPNET, IUSR_MachineName, etc. The user profile must be created on the system where DPAPI calls are made, which normally requires the user to log on to the system interactively at least once. IMPORTANT: ASP.NET applications and other programs running under the built-in system accounts, such as Windows® services running as LocalSystem, cannot use DPAPI with user-specific keys.
DPAPI with machine store When making DPAPI calls with machine-specific keys, encryption and decryption can be performed by any user or application as long as both operations are executed on the same computer. Any application - including ASP.NET - can use DPAPI with machine-specific keys. It is worth noting that this option is not secure, because it allows a malicious application installed on a system to decrypt any data encrypted by other applications on the same system using DPAPI with machine-specific keys.
Secondary entropy When an application calls the DPAPI encryption function, it can specify an optional secondary entropy ("secret" bytes) which will have to be provided by an application attempting to decrypt data. The secondary entropy can be used with either user- or machine-specific keys. It must be protected.
Data description When an application calls the DPAPI encryption function, it can specify an optional data description, which will be returned by the DPAPI decryption routine.
More info For additional information about using DPAPI in .NET, check Building Secure ASP.NET Applications, which provides a brief description of DPAPI and several code samples. For a detailed overview of DPAPI, read the Windows Data Protection article.
Resources Our FREE CipherLite.NET™ tool provides a GUI and library, which you can use to encrypt and decrypt data with DPAPI.
1 /////////////////////////////////////////////////////////////////////////////// 2 // SAMPLE: Encryption and decryption using DPAPI functions. 3 // 4 // To run this sample, create a new Visual C# project using the Console 5 // Application template and replace the contents of the Class1.cs file 6 // with the code below. 7 // 8 // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR 11 // PURPOSE. 12 // 13 // Copyright (C) 2003 Obviex(TM). All rights reserved. 14 // 15 using System; 16 using System.Text; 17 using System.Runtime.InteropServices; 18 using System.ComponentModel; 19 20 /// <summary> 21 /// Encrypts and decrypts data using DPAPI functions. 22 /// </summary> 23 public class DPAPI 24 { 25 // Wrapper for DPAPI CryptProtectData function. 26 [DllImport( "crypt32.dll", 27 SetLastError=true, 28 CharSet=System.Runtime.InteropServices.CharSet.Auto)] 29 private static extern 30 bool CryptProtectData( ref DATA_BLOB pPlainText, 31 string szDescription, 32 ref DATA_BLOB pEntropy, 33 IntPtr pReserved, 34 ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, 35 int dwFlags, 36 ref DATA_BLOB pCipherText); 37 38 // Wrapper for DPAPI CryptUnprotectData function. 39 [DllImport( "crypt32.dll", 40 SetLastError=true, 41 CharSet=System.Runtime.InteropServices.CharSet.Auto)] 42 private static extern 43 bool CryptUnprotectData(ref DATA_BLOB pCipherText, 44 ref string pszDescription, 45 ref DATA_BLOB pEntropy, 46 IntPtr pReserved, 47 ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, 48 int dwFlags, 49 ref DATA_BLOB pPlainText); 50 51 // BLOB structure used to pass data to DPAPI functions. 52 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 53 internal struct DATA_BLOB 54 { 55 public int cbData; 56 public IntPtr pbData; 57 } 58 59 // Prompt structure to be used for required parameters. 60 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 61 internal struct CRYPTPROTECT_PROMPTSTRUCT 62 { 63 public int cbSize; 64 public int dwPromptFlags; 65 public IntPtr hwndApp; 66 public string szPrompt; 67 } 68 69 // Wrapper for the NULL handle or pointer. 70 static private IntPtr NullPtr = ((IntPtr)((int)(0))); 71 72 // DPAPI key initialization flags. 73 private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1; 74 private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4; 75 76 /// <summary> 77 /// Initializes empty prompt structure. 78 /// </summary> 79 /// <param name="ps"> 80 /// Prompt parameter (which we do not actually need). 81 /// </param> 82 private static void InitPrompt(ref CRYPTPROTECT_PROMPTSTRUCT ps) 83 { 84 ps.cbSize = Marshal.SizeOf( 85 typeof(CRYPTPROTECT_PROMPTSTRUCT)); 86 ps.dwPromptFlags= 0; 87 ps.hwndApp = NullPtr; 88 ps.szPrompt = null; 89 } 90 91 /// <summary> 92 /// Initializes a BLOB structure from a byte array. 93 /// </summary> 94 /// <param name="data"> 95 /// Original data in a byte array format. 96 /// </param> 97 /// <param name="blob"> 98 /// Returned blob structure. 99 /// </param> 100 private static void InitBLOB(byte[] data, ref DATA_BLOB blob) 101 { 102 // Use empty array for null parameter. 103 if (data == null) 104 data = new byte[0]; 105 106 // Allocate memory for the BLOB data. 107 blob.pbData = Marshal.AllocHGlobal(data.Length); 108 109 // Make sure that memory allocation was successful. 110 if (blob.pbData == IntPtr.Zero) 111 throw new Exception( 112 "Unable to allocate data buffer for BLOB structure."); 113 114 // Specify number of bytes in the BLOB. 115 blob.cbData = data.Length; 116 117 // Copy data from original source to the BLOB structure. 118 Marshal.Copy(data, 0, blob.pbData, data.Length); 119 } 120 121 // Flag indicating the type of key. DPAPI terminology refers to 122 // key types as user store or machine store. 123 public enum KeyType {UserKey = 1, MachineKey}; 124 125 // It is reasonable to set default key type to user key. 126 private static KeyType defaultKeyType = KeyType.UserKey; 127 128 /// <summary> 129 /// Calls DPAPI CryptProtectData function to encrypt a plaintext 130 /// string value with a user-specific key. This function does not 131 /// specify data description and additional entropy. 132 /// </summary> 133 /// <param name="plainText"> 134 /// Plaintext data to be encrypted. 135 /// </param> 136 /// <returns> 137 /// Encrypted value in a base64-encoded format. 138 /// </returns> 139 public static string Encrypt(string plainText) 140 { 141 return Encrypt(defaultKeyType, plainText, String.Empty, 142 String.Empty); 143 } 144 145 /// <summary> 146 /// Calls DPAPI CryptProtectData function to encrypt a plaintext 147 /// string value. This function does not specify data description 148 /// and additional entropy. 149 /// </summary> 150 /// <param name="keyType"> 151 /// Defines type of encryption key to use. When user key is 152 /// specified, any application running under the same user account 153 /// as the one making this call, will be able to decrypt data. 154 /// Machine key will allow any application running on the same 155 /// computer where data were encrypted to perform decryption. 156 /// Note: If optional entropy is specifed, it will be required 157 /// for decryption. 158 /// </param> 159 /// <param name="plainText"> 160 /// Plaintext data to be encrypted. 161 /// </param> 162 /// <returns> 163 /// Encrypted value in a base64-encoded format. 164 /// </returns> 165 public static string Encrypt(KeyType keyType, string plainText) 166 { 167 return Encrypt(keyType, plainText, String.Empty, 168 String.Empty); 169 } 170 171 /// <summary> 172 /// Calls DPAPI CryptProtectData function to encrypt a plaintext 173 /// string value. This function does not specify data description. 174 /// </summary> 175 /// <param name="keyType"> 176 /// Defines type of encryption key to use. When user key is 177 /// specified, any application running under the same user account 178 /// as the one making this call, will be able to decrypt data. 179 /// Machine key will allow any application running on the same 180 /// computer where data were encrypted to perform decryption. 181 /// Note: If optional entropy is specifed, it will be required 182 /// for decryption. 183 /// </param> 184 /// <param name="plainText"> 185 /// Plaintext data to be encrypted. 186 /// </param> 187 /// <param name="entropy"> 188 /// Optional entropy which - if specified - will be required to 189 /// perform decryption. 190 /// </param> 191 /// <returns> 192 /// Encrypted value in a base64-encoded format. 193 /// </returns> 194 public static string Encrypt(KeyType keyType, 195 string plainText, 196 string entropy) 197 { 198 return Encrypt(keyType, plainText, entropy, String.Empty); 199 } 200 201 /// <summary> 202 /// Calls DPAPI CryptProtectData function to encrypt a plaintext 203 /// string value. 204 /// </summary> 205 /// <param name="keyType"> 206 /// Defines type of encryption key to use. When user key is 207 /// specified, any application running under the same user account 208 /// as the one making this call, will be able to decrypt data. 209 /// Machine key will allow any application running on the same 210 /// computer where data were encrypted to perform decryption. 211 /// Note: If optional entropy is specifed, it will be required 212 /// for decryption. 213 /// </param> 214 /// <param name="plainText"> 215 /// Plaintext data to be encrypted. 216 /// </param> 217 /// <param name="entropy"> 218 /// Optional entropy which - if specified - will be required to 219 /// perform decryption. 220 /// </param> 221 /// <param name="description"> 222 /// Optional description of data to be encrypted. If this value is 223 /// specified, it will be stored along with encrypted data and 224 /// returned as a separate value during decryption. 225 /// </param> 226 /// <returns> 227 /// Encrypted value in a base64-encoded format. 228 /// </returns> 229 public static string Encrypt(KeyType keyType, 230 string plainText, 231 string entropy, 232 string description) 233 { 234 // Make sure that parameters are valid. 235 if (plainText == null) plainText = String.Empty; 236 if (entropy == null) entropy = String.Empty; 237 238 // Call encryption routine and convert returned bytes into 239 // a base64-encoded value. 240 return Convert.ToBase64String( 241 Encrypt(keyType, 242 Encoding.UTF8.GetBytes(plainText), 243 Encoding.UTF8.GetBytes(entropy), 244 description)); 245 } 246 247 /// <summary> 248 /// Calls DPAPI CryptProtectData function to encrypt an array of 249 /// plaintext bytes. 250 /// </summary> 251 /// <param name="keyType"> 252 /// Defines type of encryption key to use. When user key is 253 /// specified, any application running under the same user account 254 /// as the one making this call, will be able to decrypt data. 255 /// Machine key will allow any application running on the same 256 /// computer where data were encrypted to perform decryption. 257 /// Note: If optional entropy is specifed, it will be required 258 /// for decryption. 259 /// </param> 260 /// <param name="plainTextBytes"> 261 /// Plaintext data to be encrypted. 262 /// </param> 263 /// <param name="entropyBytes"> 264 /// Optional entropy which - if specified - will be required to 265 /// perform decryption. 266 /// </param> 267 /// <param name="description"> 268 /// Optional description of data to be encrypted. If this value is 269 /// specified, it will be stored along with encrypted data and 270 /// returned as a separate value during decryption. 271 /// </param> 272 /// <returns> 273 /// Encrypted value. 274 /// </returns> 275 public static byte[] Encrypt(KeyType keyType, 276 byte[] plainTextBytes, 277 byte[] entropyBytes, 278 string description) 279 { 280 // Make sure that parameters are valid. 281 if (plainTextBytes == null) plainTextBytes = new byte[0]; 282 if (entropyBytes == null) entropyBytes = new byte[0]; 283 if (description == null) description = String.Empty; 284 285 // Create BLOBs to hold data. 286 DATA_BLOB plainTextBlob = new DATA_BLOB(); 287 DATA_BLOB cipherTextBlob = new DATA_BLOB(); 288 DATA_BLOB entropyBlob = new DATA_BLOB(); 289 290 // We only need prompt structure because it is a required 291 // parameter. 292 CRYPTPROTECT_PROMPTSTRUCT prompt = 293 new CRYPTPROTECT_PROMPTSTRUCT(); 294 InitPrompt(ref prompt); 295 296 try 297 { 298 // Convert plaintext bytes into a BLOB structure. 299 try 300 { 301 InitBLOB(plainTextBytes, ref plainTextBlob); 302 } 303 catch (Exception ex) 304 { 305 throw new Exception( 306 "Cannot initialize plaintext BLOB.", ex); 307 } 308 309 // Convert entropy bytes into a BLOB structure. 310 try 311 { 312 InitBLOB(entropyBytes, ref entropyBlob); 313 } 314 catch (Exception ex) 315 { 316 throw new Exception( 317 "Cannot initialize entropy BLOB.", ex); 318 } 319 320 // Disable any types of UI. 321 int flags = CRYPTPROTECT_UI_FORBIDDEN; 322 323 // When using machine-specific key, set up machine flag. 324 if (keyType == KeyType.MachineKey) 325 flags |= CRYPTPROTECT_LOCAL_MACHINE; 326 327 // Call DPAPI to encrypt data. 328 bool success = CryptProtectData(ref plainTextBlob, 329 description, 330 ref entropyBlob, 331 IntPtr.Zero, 332 ref prompt, 333 flags, 334 ref cipherTextBlob); 335 // Check the result. 336 if (!success) 337 { 338 // If operation failed, retrieve last Win32 error. 339 int errCode = Marshal.GetLastWin32Error(); 340 341 // Win32Exception will contain error message corresponding 342 // to the Windows error code. 343 throw new Exception( 344 "CryptProtectData failed.", new Win32Exception(errCode)); 345 } 346 347 // Allocate memory to hold ciphertext. 348 byte[] cipherTextBytes = new byte[cipherTextBlob.cbData]; 349 350 // Copy ciphertext from the BLOB to a byte array. 351 Marshal.Copy(cipherTextBlob.pbData, 352 cipherTextBytes, 353 0, 354 cipherTextBlob.cbData); 355 356 // Return the result. 357 return cipherTextBytes; 358 } 359 catch (Exception ex) 360 { 361 throw new Exception("DPAPI was unable to encrypt data.", ex); 362 } 363 // Free all memory allocated for BLOBs. 364 finally 365 { 366 if (plainTextBlob.pbData != IntPtr.Zero) 367 Marshal.FreeHGlobal(plainTextBlob.pbData); 368 369 if (cipherTextBlob.pbData != IntPtr.Zero) 370 Marshal.FreeHGlobal(cipherTextBlob.pbData); 371 372 if (entropyBlob.pbData != IntPtr.Zero) 373 Marshal.FreeHGlobal(entropyBlob.pbData); 374 } 375 } 376 377 /// <summary> 378 /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 379 /// This function does not use additional entropy and does not 380 /// return data description. 381 /// </summary> 382 /// <param name="cipherText"> 383 /// Encrypted data formatted as a base64-encoded string. 384 /// </param> 385 /// <returns> 386 /// Decrypted data returned as a UTF-8 string. 387 /// </returns> 388 /// <remarks> 389 /// When decrypting data, it is not necessary to specify which 390 /// type of encryption key to use: user-specific or 391 /// machine-specific; DPAPI will figure it out by looking at 392 /// the signature of encrypted data. 393 /// </remarks> 394 public static string Decrypt(string cipherText) 395 { 396 string description; 397 398 return Decrypt(cipherText, String.Empty, out description); 399 } 400 401 /// <summary> 402 /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 403 /// This function does not use additional entropy. 404 /// </summary> 405 /// <param name="cipherText"> 406 /// Encrypted data formatted as a base64-encoded string. 407 /// </param> 408 /// <param name="description"> 409 /// Returned description of data specified during encryption. 410 /// </param> 411 /// <returns> 412 /// Decrypted data returned as a UTF-8 string. 413 /// </returns> 414 /// <remarks> 415 /// When decrypting data, it is not necessary to specify which 416 /// type of encryption key to use: user-specific or 417 /// machine-specific; DPAPI will figure it out by looking at 418 /// the signature of encrypted data. 419 /// </remarks> 420 public static string Decrypt( string cipherText, 421 out string description) 422 { 423 return Decrypt(cipherText, String.Empty, out description); 424 } 425 426 /// <summary> 427 /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 428 /// </summary> 429 /// <param name="cipherText"> 430 /// Encrypted data formatted as a base64-encoded string. 431 /// </param> 432 /// <param name="entropy"> 433 /// Optional entropy, which is required if it was specified during 434 /// encryption. 435 /// </param> 436 /// <param name="description"> 437 /// Returned description of data specified during encryption. 438 /// </param> 439 /// <returns> 440 /// Decrypted data returned as a UTF-8 string. 441 /// </returns> 442 /// <remarks> 443 /// When decrypting data, it is not necessary to specify which 444 /// type of encryption key to use: user-specific or 445 /// machine-specific; DPAPI will figure it out by looking at 446 /// the signature of encrypted data. 447 /// </remarks> 448 public static string Decrypt( string cipherText, 449 string entropy, 450 out string description) 451 { 452 // Make sure that parameters are valid. 453 if (entropy == null) entropy = String.Empty; 454 455 return Encoding.UTF8.GetString( 456 Decrypt( Convert.FromBase64String(cipherText), 457 Encoding.UTF8.GetBytes(entropy), 458 out description)); 459 } 460 461 /// <summary> 462 /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 463 /// </summary> 464 /// <param name="cipherTextBytes"> 465 /// Encrypted data. 466 /// </param> 467 /// <param name="entropyBytes"> 468 /// Optional entropy, which is required if it was specified during 469 /// encryption. 470 /// </param> 471 /// <param name="description"> 472 /// Returned description of data specified during encryption. 473 /// </param> 474 /// <returns> 475 /// Decrypted data bytes. 476 /// </returns> 477 /// <remarks> 478 /// When decrypting data, it is not necessary to specify which 479 /// type of encryption key to use: user-specific or 480 /// machine-specific; DPAPI will figure it out by looking at 481 /// the signature of encrypted data. 482 /// </remarks> 483 public static byte[] Decrypt( byte[] cipherTextBytes, 484 byte[] entropyBytes, 485 out string description) 486 { 487 // Create BLOBs to hold data. 488 DATA_BLOB plainTextBlob = new DATA_BLOB(); 489 DATA_BLOB cipherTextBlob = new DATA_BLOB(); 490 DATA_BLOB entropyBlob = new DATA_BLOB(); 491 492 // We only need prompt structure because it is a required 493 // parameter. 494 CRYPTPROTECT_PROMPTSTRUCT prompt = 495 new CRYPTPROTECT_PROMPTSTRUCT(); 496 InitPrompt(ref prompt); 497 498 // Initialize description string. 499 description = String.Empty; 500 501 try 502 { 503 // Convert ciphertext bytes into a BLOB structure. 504 try 505 { 506 InitBLOB(cipherTextBytes, ref cipherTextBlob); 507 } 508 catch (Exception ex) 509 { 510 throw new Exception( 511 "Cannot initialize ciphertext BLOB.", ex); 512 } 513 514 // Convert entropy bytes into a BLOB structure. 515 try 516 { 517 InitBLOB(entropyBytes, ref entropyBlob); 518 } 519 catch (Exception ex) 520 { 521 throw new Exception( 522 "Cannot initialize entropy BLOB.", ex); 523 } 524 525 // Disable any types of UI. CryptUnprotectData does not 526 // mention CRYPTPROTECT_LOCAL_MACHINE flag in the list of 527 // supported flags so we will not set it up. 528 int flags = CRYPTPROTECT_UI_FORBIDDEN; 529 530 // Call DPAPI to decrypt data. 531 bool success = CryptUnprotectData(ref cipherTextBlob, 532 ref description, 533 ref entropyBlob, 534 IntPtr.Zero, 535 ref prompt, 536 flags, 537 ref plainTextBlob); 538 539 // Check the result. 540 if (!success) 541 { 542 // If operation failed, retrieve last Win32 error. 543 int errCode = Marshal.GetLastWin32Error(); 544 545 // Win32Exception will contain error message corresponding 546 // to the Windows error code. 547 throw new Exception( 548 "CryptUnprotectData failed.", new Win32Exception(errCode)); 549 } 550 551 // Allocate memory to hold plaintext. 552 byte[] plainTextBytes = new byte[plainTextBlob.cbData]; 553 554 // Copy ciphertext from the BLOB to a byte array. 555 Marshal.Copy(plainTextBlob.pbData, 556 plainTextBytes, 557 0, 558 plainTextBlob.cbData); 559 560 // Return the result. 561 return plainTextBytes; 562 } 563 catch (Exception ex) 564 { 565 throw new Exception("DPAPI was unable to decrypt data.", ex); 566 } 567 // Free all memory allocated for BLOBs. 568 finally 569 { 570 if (plainTextBlob.pbData != IntPtr.Zero) 571 Marshal.FreeHGlobal(plainTextBlob.pbData); 572 573 if (cipherTextBlob.pbData != IntPtr.Zero) 574 Marshal.FreeHGlobal(cipherTextBlob.pbData); 575 576 if (entropyBlob.pbData != IntPtr.Zero) 577 Marshal.FreeHGlobal(entropyBlob.pbData); 578 } 579 } 580 } 581 582 /// <summary> 583 /// Demonstrates the use of DPAPI functions to encrypt and decrypt data. 584 /// </summary> 585 public class DPAPITest 586 { 587 /// <summary> 588 /// The main entry point for the application. 589 /// </summary> 590 [STAThread] 591 static void Main(string[] args) 592 { 593 try 594 { 595 string text = "Hello, world!"; 596 string entropy = null; 597 string description; 598 599 Console.WriteLine("Plaintext: {0}\r\n", text); 600 601 // Call DPAPI to encrypt data with user-specific key. 602 string encrypted = DPAPI.Encrypt( DPAPI.KeyType.UserKey, 603 text, 604 entropy, 605 "My Data"); 606 Console.WriteLine("Encrypted: {0}\r\n", encrypted); 607 608 // Call DPAPI to decrypt data. 609 string decrypted = DPAPI.Decrypt( encrypted, 610 entropy, 611 out description); 612 Console.WriteLine("Decrypted: {0} <<<{1}>>>\r\n", 613 decrypted, description); 614 } 615 catch (Exception ex) 616 { 617 while (ex != null) 618 { 619 Console.WriteLine(ex.Message); 620 ex = ex.InnerException; 621 } 622 } 623 } 624 } 625 // 626 // END OF FILE 627 ///////////////////////////////////////////////////////////////////////////////