วิธีบันทึกชื่อผู้ใช้ / รหัสผ่านอย่างปลอดภัย (ในเครื่อง)?


107

ฉันกำลังสร้างแอปพลิเคชัน Windows ซึ่งคุณต้องเข้าสู่ระบบก่อน
รายละเอียดบัญชีประกอบด้วยชื่อผู้ใช้และรหัสผ่านและจำเป็นต้องบันทึกไว้ในเครื่อง
เป็นเพียงเรื่องของความปลอดภัยดังนั้นบุคคลอื่นที่ใช้คอมพิวเตอร์เครื่องเดียวกันจึงไม่สามารถเห็นข้อมูลส่วนตัวของทุกคน
วิธีใดดีที่สุด / ปลอดภัยที่สุดในการบันทึกข้อมูลนี้

ฉันไม่ต้องการใช้ฐานข้อมูลดังนั้นฉันจึงลองทำบางอย่างกับไฟล์ทรัพยากร
แต่เนื่องจากฉันยังใหม่กับสิ่งนี้ฉันจึงไม่แน่ใจว่ากำลังทำอะไรอยู่และควรหาทางแก้ไขที่ไหน


6
ก่อนอื่นอย่าบันทึกรหัสผ่าน แฮช (อาจมีค่าเกลือ) และบันทึกแทน
carlosfigueira

"ผู้ใช้" คุณหมายถึงผู้ใช้ Windows ทั่วไปหรืออย่างอื่น? (ฉันคิดว่าคุณหมายถึงบางคนเป็นเจ้าของ "ผู้ใช้" เนื่องจากผู้ใช้ Windows ทั่วไปไม่สามารถดูข้อมูลของกันและกันได้ ... )
Alexei Levenkov

ฉันแก้ไขชื่อของคุณแล้ว โปรดดู " คำถามควรมี" แท็ก "ในชื่อเรื่องหรือไม่ " โดยที่ฉันทามติคือ "ไม่ควรไม่ควร"
John Saunders

@ จอห์นแซนเดอร์สเอาล่ะขอโทษที่ฉันไม่รู้
Robin

2
โซลูชันสุดท้ายที่มีซอร์สโค้ดแบบเต็มหรือไม่
Kiquenet

คำตอบ:


161

หากคุณกำลังจะตรวจสอบ / ตรวจสอบชื่อผู้ใช้และรหัสผ่านที่ป้อนให้ใช้คลาสRfc2898DerivedBytes (หรือที่เรียกว่า Password Based Key Derivation Function 2 หรือ PBKDF2) สิ่งนี้ปลอดภัยกว่าการใช้การเข้ารหัสเช่น Triple DES หรือ AES เนื่องจากไม่มีวิธีที่เป็นประโยชน์ในการเปลี่ยนจากผลลัพธ์ของ RFC2898DerivedBytes กลับไปที่รหัสผ่าน คุณสามารถเปลี่ยนจากรหัสผ่านไปยังผลลัพธ์เท่านั้น ดูการใช้แฮชของรหัสผ่าน SHA1 เป็นเกลือเมื่อได้รับคีย์การเข้ารหัสและ IV จากสตริงรหัสผ่านหรือไม่ สำหรับตัวอย่างและการสนทนาสำหรับ. Net หรือString เข้ารหัส / ถอดรหัสด้วยรหัสผ่าน c # Metro Styleสำหรับ WinRT / Metro

หากคุณกำลังจัดเก็บรหัสผ่านเพื่อนำมาใช้เช่นการจัดหาให้บุคคลที่สามใช้API ของ Windows คุ้มครองข้อมูล (DPAPI) สิ่งนี้ใช้คีย์ที่สร้างและป้องกันระบบปฏิบัติการและอัลกอริทึมการเข้ารหัสTriple DESเพื่อเข้ารหัสและถอดรหัสข้อมูล ซึ่งหมายความว่าแอปพลิเคชันของคุณไม่ต้องกังวลกับการสร้างและปกป้องคีย์การเข้ารหัสซึ่งเป็นข้อกังวลหลักเมื่อใช้การเข้ารหัส

ใน C # ใช้คลาสSystem.Security.Cryptography.ProtectedData ตัวอย่างเช่นในการเข้ารหัสข้อมูลให้ใช้ProtectedData.Protect():

// Data to protect. Convert a string to a byte[] using Encoding.UTF8.GetBytes().
byte[] plaintext; 

// Generate additional entropy (will be used as the Initialization vector)
byte[] entropy = new byte[20];
using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
    rng.GetBytes(entropy);
}

byte[] ciphertext = ProtectedData.Protect(plaintext, entropy,
    DataProtectionScope.CurrentUser);

จัดเก็บเอนโทรปีและไซเฟอร์เท็กซ์อย่างปลอดภัยเช่นในไฟล์หรือรีจิสตรีคีย์ที่มีการตั้งค่าสิทธิ์เพื่อให้เฉพาะผู้ใช้ปัจจุบันเท่านั้นที่สามารถอ่านได้ ในการเข้าถึงข้อมูลดั้งเดิมให้ใช้ProtectedData.Unprotect():

byte[] plaintext= ProtectedData.Unprotect(ciphertext, entropy,
    DataProtectionScope.CurrentUser);

โปรดทราบว่ามีข้อควรพิจารณาด้านความปลอดภัยเพิ่มเติม ตัวอย่างเช่นหลีกเลี่ยงการเก็บความลับเช่นรหัสผ่านเป็นไฟล์string. สตริงไม่เปลี่ยนรูปเนื่องจากไม่สามารถแจ้งในหน่วยความจำได้ดังนั้นคนที่ดูหน่วยความจำของแอปพลิเคชันหรือการถ่ายโอนข้อมูลหน่วยความจำอาจเห็นรหัสผ่าน ใช้SecureStringหรือ byte [] แทนและอย่าลืมทิ้งหรือให้เป็นศูนย์ทันทีที่ไม่จำเป็นต้องใช้รหัสผ่านอีกต่อไป


สวัสดีฉันลองแล้ว แต่พบข้อผิดพลาดที่ entropy = rng.GetBytes (20) บอกว่า: ไม่สามารถแปลงจาก int เป็นไบต์ []
โรบิน

@CrispyGMR ฉันได้แก้ไขส่วนของรหัสนั้นในคำตอบ จับดี.
akton

ขอบคุณมาก. ฉันใช้ md5 ในการแฮชในตอนแรก แต่ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้ วิธีนี้ดูเหมือนจะปลอดภัยกว่า อีกหนึ่งคำถามว่า ฉันต้องการบันทึกข้อมูลแบบนี้ในไฟล์ข้อความ ฉันเห็นว่ามันเป็นเพียงอักขระสุ่มจำนวนมากเมื่อฉันเปิดไฟล์ของฉัน แต่มันปลอดภัยพอที่จะทำเช่นนี้หรือไม่? หรือคุณแนะนำวิธีอื่นในการจัดเก็บข้อมูล?
Robin

2
ดูเหมือนว่าคลาสนี้รู้จักกันในชื่อ Rfc2898DeriveBytes (ตัวอักษรขนาดเล็ก,. net 4.5 และ 4.6) และสามารถพบได้ที่นี่: Namespace: System.Security.Cryptography Assembly: mscorlib (ใน mscorlib.dll)
Dashu

2
ข้อมูลมาก แต่ผมคิดว่าจุดรวมของการใช้ProtectedDataเพื่อให้ฉันไม่จำเป็นต้องกังวลเกี่ยวกับร้านเอนโทรปีและ ciphertext ปลอดภัย ... เท่านั้นดังนั้นผู้ใช้ปัจจุบันสามารถอ่านได้ ฉันคิดว่ามันมีความเรียบง่ายที่ฉันสามารถจัดเก็บได้ แต่ก็สะดวกและยังคงมีเพียง CurrentUser เท่านั้นที่สามารถถอดรหัสได้ entropyพารามิเตอร์ยังเป็นตัวเลือกและปรากฏคล้ายกับ IV ที่เป็นเอกลักษณ์มากกว่าเรื่องความลับ ด้วยเหตุนี้ค่าอาจถูกละเว้นหรือเข้ารหัสในโปรแกรมในสถานการณ์ที่การเปลี่ยนแปลงและการอัปเดตของข้อความธรรมดาไม่บ่อยนัก
antak

8

ฉันเคยใช้สิ่งนี้มาก่อนและฉันคิดว่าเพื่อให้แน่ใจว่าข้อมูลรับรองยังคงมีอยู่และวิธีที่ปลอดภัยที่สุดคือ

  1. คุณสามารถเขียนลงในไฟล์กำหนดค่าแอปโดยใช้ConfigurationManagerคลาส
  2. การรักษาความปลอดภัยรหัสผ่านโดยใช้SecureStringคลาส
  3. จากนั้นเข้ารหัสโดยใช้เครื่องมือในCryptographyเนมสเปซ

ลิงค์นี้จะเป็นประโยชน์อย่างยิ่งที่ฉันหวังว่า: คลิกที่นี่


4

DPAPI มีไว้เพื่อการนี้เท่านั้น ใช้ DPAPI เพื่อเข้ารหัสรหัสผ่านในครั้งแรกที่ผู้ใช้เข้าสู่คือเก็บไว้ในตำแหน่งที่ปลอดภัย (รีจิสตรีของผู้ใช้ไดเร็กทอรีข้อมูลแอปพลิเคชันของผู้ใช้เป็นตัวเลือกบางอย่าง) เมื่อใดก็ตามที่เปิดแอปให้ตรวจสอบตำแหน่งเพื่อดูว่ามีคีย์ของคุณอยู่หรือไม่หากใช้ DPAPI ในการถอดรหัสและอนุญาตให้เข้าถึงมิฉะนั้นจะปฏิเสธ


4

สิ่งนี้ใช้ได้กับ Windows เท่านั้นดังนั้นหากคุณกำลังวางแผนที่จะใช้ dotnet core ข้ามแพลตฟอร์มคุณจะต้องดูที่อื่น ดูhttps://github.com/dotnet/corefx/blob/master/Documentation/architecture/cross-platform-cryptography.md


ลิงก์นี้ตายแล้ว (404)
KarloX

2

ฉันต้องการเข้ารหัสและถอดรหัสสตริงเป็นสตริงที่อ่านได้

นี่เป็นตัวอย่างรวดเร็วง่ายมากใน C # Visual Studio 2019 WinForms @Pradipขึ้นอยู่กับคำตอบจาก

คลิกขวาที่โปรเจ็กต์> คุณสมบัติ> การตั้งค่า> สร้างusernameและpasswordการตั้งค่า

ป้อนคำอธิบายภาพที่นี่

ตอนนี้คุณสามารถใช้ประโยชน์จากการตั้งค่าที่คุณเพิ่งสร้างขึ้น ที่นี่ฉันบันทึกusernameและpasswordเข้ารหัสเฉพาะpasswordในฟิลด์ค่าที่น่านับถือในuser.configไฟล์

ตัวอย่างสตริงที่เข้ารหัสในuser.configไฟล์

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <secure_password_store.Properties.Settings>
            <setting name="username" serializeAs="String">
                <value>admin</value>
            </setting>
            <setting name="password" serializeAs="String">
                <value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAQpgaPYIUq064U3o6xXkQOQAAAAACAAAAAAAQZgAAAAEAACAAAABlQQ8OcONYBr9qUhH7NeKF8bZB6uCJa5uKhk97NdH93AAAAAAOgAAAAAIAACAAAAC7yQicDYV5DiNp0fHXVEDZ7IhOXOrsRUbcY0ziYYTlKSAAAACVDQ+ICHWooDDaUywJeUOV9sRg5c8q6/vizdq8WtPVbkAAAADciZskoSw3g6N9EpX/8FOv+FeExZFxsm03i8vYdDHUVmJvX33K03rqiYF2qzpYCaldQnRxFH9wH2ZEHeSRPeiG</value>
            </setting>
        </secure_password_store.Properties.Settings>
    </userSettings>
</configuration>

ป้อนคำอธิบายภาพที่นี่

รหัสเต็ม

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace secure_password_store
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Exit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void Login_Click(object sender, EventArgs e)
        {
            if (checkBox1.Checked == true)
            {
                Properties.Settings.Default.username = textBox1.Text;
                Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
                Properties.Settings.Default.Save();
            }
            else if (checkBox1.Checked == false)
            {
                Properties.Settings.Default.username = "";
                Properties.Settings.Default.password = "";
                Properties.Settings.Default.Save();
            }
            MessageBox.Show("{\"data\": \"some data\"}","Login Message Alert",MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        private void DecryptString_Click(object sender, EventArgs e)
        {
            SecureString password = DecryptString(Properties.Settings.Default.password);
            string readable = ToInsecureString(password);
            textBox4.AppendText(readable + Environment.NewLine);
        }
        private void Form_Load(object sender, EventArgs e)
        {
            //textBox1.Text = "UserName";
            //textBox2.Text = "Password";
            if (Properties.Settings.Default.username != string.Empty)
            {
                textBox1.Text = Properties.Settings.Default.username;
                checkBox1.Checked = true;
                SecureString password = DecryptString(Properties.Settings.Default.password);
                string readable = ToInsecureString(password);
                textBox2.Text = readable;
            }
            groupBox1.Select();
        }


        static byte[] entropy = Encoding.Unicode.GetBytes("SaLtY bOy 6970 ePiC");

        public static string EncryptString(SecureString input)
        {
            byte[] encryptedData = ProtectedData.Protect(Encoding.Unicode.GetBytes(ToInsecureString(input)),entropy,DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }

        public static SecureString DecryptString(string encryptedData)
        {
            try
            {
                byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encryptedData),entropy,DataProtectionScope.CurrentUser);
                return ToSecureString(Encoding.Unicode.GetString(decryptedData));
            }
            catch
            {
                return new SecureString();
            }
        }

        public static SecureString ToSecureString(string input)
        {
            SecureString secure = new SecureString();
            foreach (char c in input)
            {
                secure.AppendChar(c);
            }
            secure.MakeReadOnly();
            return secure;
        }

        public static string ToInsecureString(SecureString input)
        {
            string returnValue = string.Empty;
            IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
            try
            {
                returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
            }
            return returnValue;
        }

        private void EncryptString_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
            textBox3.AppendText(Properties.Settings.Default.password.ToString() + Environment.NewLine);
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.