SVN
This commit is contained in:
222
MasterPasswordForm.cs
Normal file
222
MasterPasswordForm.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Isopoh.Cryptography.Argon2;
|
||||
|
||||
namespace SimPas2_Windows
|
||||
{
|
||||
internal class MasterPasswordForm : Form
|
||||
{
|
||||
private readonly string mDatabasePath;
|
||||
public byte[] EncryptionKey { get; private set; }
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
public MasterPasswordForm(string databasePath)
|
||||
{
|
||||
mDatabasePath = databasePath;
|
||||
InitializeComponent();
|
||||
this.Icon = new Icon("simpas.ico");
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.Text = "マスターパスワード";
|
||||
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.StartPosition = FormStartPosition.CenterScreen;
|
||||
this.Width = 300;
|
||||
this.Height = 150;
|
||||
|
||||
var layout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2,
|
||||
Padding = new Padding(10)
|
||||
};
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
TextBox masPasswordTextbox = new TextBox { Dock = DockStyle.Fill, UseSystemPasswordChar = true };
|
||||
Button masOkButton = new Button { Text = "OK", Dock = DockStyle.Right, Width = 80 };
|
||||
Button masCancelButton = new Button { Text = "キャンセル", Dock = DockStyle.Right, Width = 80 };
|
||||
|
||||
masOkButton.Click += (s, e) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(masPasswordTextbox.Text))
|
||||
{
|
||||
MessageBox.Show("マスターパスワードは必須です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (VerifyOrSetMasterPassword(masPasswordTextbox.Text))
|
||||
{
|
||||
EncryptionKey = DeriveKey(masPasswordTextbox.Text);
|
||||
IsValid = VerifyKey(EncryptionKey);
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("不正なマスターパスワード。もう一度試してみて下さい。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"パスワードエラー:{ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
};
|
||||
|
||||
masCancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
|
||||
|
||||
layout.Controls.Add(masPasswordTextbox, 1, 0);
|
||||
|
||||
FlowLayoutPanel buttonPanel = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
FlowDirection = FlowDirection.RightToLeft
|
||||
};
|
||||
buttonPanel.Controls.Add(masCancelButton);
|
||||
buttonPanel.Controls.Add(masOkButton);
|
||||
|
||||
layout.Controls.Add(buttonPanel, 0, 1);
|
||||
layout.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
this.Controls.Add(layout);
|
||||
this.AcceptButton = masOkButton;
|
||||
this.CancelButton = masCancelButton;
|
||||
}
|
||||
|
||||
private bool VerifyOrSetMasterPassword(string password)
|
||||
{
|
||||
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
SqliteCommand createTable = conn.CreateCommand();
|
||||
createTable.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS Config (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Key VARCHAR(255) NOT NULL,
|
||||
Value TEXT
|
||||
)";
|
||||
createTable.ExecuteNonQuery();
|
||||
|
||||
SqliteCommand select = conn.CreateCommand();
|
||||
select.CommandText = "SELECT Value FROM Config WHERE Key = 'MasterPasswordHash'";
|
||||
using (SqliteDataReader reader = select.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
string storedHash = reader.GetString(0);
|
||||
return Argon2.Verify(storedHash, password);
|
||||
}
|
||||
}
|
||||
|
||||
string passwordHash = Argon2.Hash(password);
|
||||
SqliteCommand insert = conn.CreateCommand();
|
||||
insert.CommandText = "INSERT INTO Config (Key, Value) VALUES ('MasterPasswordHash', $value)";
|
||||
insert.Parameters.AddWithValue("$value", passwordHash);
|
||||
insert.ExecuteNonQuery();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] DeriveKey(string password)
|
||||
{
|
||||
byte[] salt = GetOrCreateSalt();
|
||||
using (Rfc2898DeriveBytes derive = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256))
|
||||
{
|
||||
return derive.GetBytes(32); // AES-256向け32バイト
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetOrCreateSalt()
|
||||
{
|
||||
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
SqliteCommand createTable = conn.CreateCommand();
|
||||
createTable.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS Config (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Key VARCHAR(255) NOT NULL,
|
||||
Value TEXT
|
||||
)";
|
||||
createTable.ExecuteNonQuery();
|
||||
|
||||
SqliteCommand select = conn.CreateCommand();
|
||||
select.CommandText = "SELECT Value FROM Config WHERE Key = 'Salt'";
|
||||
using (SqliteDataReader reader = select.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
byte[] salt = new byte[16];
|
||||
reader.GetBytes(0, 0, salt, 0, 16);
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] newSalt = RandomNumberGenerator.GetBytes(16);
|
||||
SqliteCommand insert = conn.CreateCommand();
|
||||
insert.CommandText = "INSERT INTO Config (Key, Value) VALUES ('Salt', $value)";
|
||||
insert.Parameters.AddWithValue("$value", newSalt);
|
||||
insert.ExecuteNonQuery();
|
||||
|
||||
return newSalt;
|
||||
}
|
||||
}
|
||||
|
||||
private bool VerifyKey(byte[] key)
|
||||
{
|
||||
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||
{
|
||||
conn.Open();
|
||||
SqliteCommand com = conn.CreateCommand();
|
||||
com.CommandText = "SELECT COUNT(*) FROM Passwords";
|
||||
long count = (long)com.ExecuteScalar();
|
||||
if (count == 0) return true;
|
||||
|
||||
com.CommandText = "SELECT Password FROM Passwords LIMIT 1";
|
||||
using (SqliteDataReader reader = com.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
try
|
||||
{
|
||||
string encryptedPassword = reader.GetString(0);
|
||||
byte[] combined = Convert.FromBase64String(encryptedPassword);
|
||||
byte[] iv = new byte[16];
|
||||
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||
|
||||
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||
{
|
||||
decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user