This repository has been archived on 2026-05-26. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
SimPas2-Windows/MasterPasswordForm.cs
2026-01-21 03:07:35 +09:00

223 lines
7.3 KiB
C#

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;
}
}
}
}