ฉันคิดว่ามันจะดีที่สุดสำหรับSecureString
ฟังก์ชั่นที่ขึ้นต่อกันเพื่อแค็ปซูลลอจิกที่ขึ้นอยู่กับฟังก์ชันที่ไม่ระบุชื่อเพื่อการควบคุมที่ดีขึ้นของสตริงถอดรหัสในหน่วยความจำ
การใช้งานสำหรับการถอดรหัส SecureStrings ในตัวอย่างนี้จะ:
- ตรึงสตริงในหน่วยความจำ (ซึ่งเป็นสิ่งที่คุณต้องการทำ แต่ดูเหมือนจะหายไปจากคำตอบส่วนใหญ่ที่นี่)
- ผ่านการอ้างอิงถึงผู้แทน Func / Action
- ขัดออกจากหน่วยความจำและปล่อย GC ใน
finally
บล็อก
เห็นได้ชัดว่าสิ่งนี้ทำให้ง่ายขึ้นมากในการ "สร้างมาตรฐาน" และรักษาผู้โทรไว้กับการพึ่งพาทางเลือกที่ต้องการน้อยกว่า:
- การส่งคืนสตริงถอดรหัสจาก
string DecryptSecureString(...)
ฟังก์ชันตัวช่วย
- ทำซ้ำรหัสนี้ทุกที่ที่จำเป็น
ประกาศที่นี่คุณมีสองตัวเลือก:
static T DecryptSecureString<T>
ซึ่งช่วยให้คุณสามารถเข้าถึงผลลัพธ์ของFunc
ผู้รับมอบสิทธิ์จากผู้โทร (ดังที่แสดงในDecryptSecureStringWithFunc
วิธีการทดสอบ)
static void DecryptSecureString
เป็นเพียงรุ่น "โมฆะ" ซึ่งใช้Action
ผู้รับมอบสิทธิ์ในกรณีที่คุณไม่ต้องการ / จำเป็นต้องส่งคืนสิ่งใด (ตามที่แสดงในDecryptSecureStringWithAction
วิธีการทดสอบ)
ตัวอย่างการใช้งานสำหรับทั้งสองสามารถพบได้ในStringsTest
ชั้นเรียนรวม
Strings.cs
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace SecurityUtils
{
public partial class Strings
{
/// <summary>
/// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
/// </summary>
/// <typeparam name="T">Generic type returned by Func delegate</typeparam>
/// <param name="action">Func delegate which will receive the decrypted password pinned in memory as a String object</param>
/// <returns>Result of Func delegate</returns>
public static T DecryptSecureString<T>(SecureString secureString, Func<string, T> action)
{
var insecureStringPointer = IntPtr.Zero;
var insecureString = String.Empty;
var gcHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);
try
{
insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(secureString);
insecureString = Marshal.PtrToStringUni(insecureStringPointer);
return action(insecureString);
}
finally
{
//clear memory immediately - don't wait for garbage collector
fixed(char* ptr = insecureString )
{
for(int i = 0; i < insecureString.Length; i++)
{
ptr[i] = '\0';
}
}
insecureString = null;
gcHandler.Free();
Marshal.ZeroFreeGlobalAllocUnicode(insecureStringPointer);
}
}
/// <summary>
/// Runs DecryptSecureString with support for Action to leverage void return type
/// </summary>
/// <param name="secureString"></param>
/// <param name="action"></param>
public static void DecryptSecureString(SecureString secureString, Action<string> action)
{
DecryptSecureString<int>(secureString, (s) =>
{
action(s);
return 0;
});
}
}
}
StringsTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security;
namespace SecurityUtils.Test
{
[TestClass]
public class StringsTest
{
[TestMethod]
public void DecryptSecureStringWithFunc()
{
// Arrange
var secureString = new SecureString();
foreach (var c in "UserPassword123".ToCharArray())
secureString.AppendChar(c);
secureString.MakeReadOnly();
// Act
var result = Strings.DecryptSecureString<bool>(secureString, (password) =>
{
return password.Equals("UserPassword123");
});
// Assert
Assert.IsTrue(result);
}
[TestMethod]
public void DecryptSecureStringWithAction()
{
// Arrange
var secureString = new SecureString();
foreach (var c in "UserPassword123".ToCharArray())
secureString.AppendChar(c);
secureString.MakeReadOnly();
// Act
var result = false;
Strings.DecryptSecureString(secureString, (password) =>
{
result = password.Equals("UserPassword123");
});
// Assert
Assert.IsTrue(result);
}
}
}
เห็นได้ชัดว่านี่ไม่ได้ป้องกันการใช้ฟังก์ชันนี้ในทางที่ผิดดังนั้นโปรดระวังอย่าทำสิ่งนี้:
[TestMethod]
public void DecryptSecureStringWithAction()
{
// Arrange
var secureString = new SecureString();
foreach (var c in "UserPassword123".ToCharArray())
secureString.AppendChar(c);
secureString.MakeReadOnly();
// Act
string copyPassword = null;
Strings.DecryptSecureString(secureString, (password) =>
{
copyPassword = password; // Please don't do this!
});
// Assert
Assert.IsNull(copyPassword); // Fails
}
การเข้ารหัสที่มีความสุข!