This commit is contained in:
2026-01-21 03:07:35 +09:00
commit 5c7516f60f
19 changed files with 15472 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
internal class CreditcardManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public CreditcardManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string cardnumber, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Cc
WHERE UPPER(Cardnumber) = UPPER(@cardnumber)";
com.Parameters.AddWithValue("@cardnumber", cardnumber);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddCc(string brand, string cardnumber, string expiration, string cvc, string holdername, string note)
{
if (string.IsNullOrWhiteSpace(brand) || string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(expiration) || string.IsNullOrWhiteSpace(cvc) || string.IsNullOrWhiteSpace(holdername))
{
string err = mCulture == mJpLang
? "ブランド、カード番号、氏名、有効期限及び、CVCを御入力下さい。"
: "Please fill in the brand, card number, full name, expiration, and CVC.";
throw new ArgumentException(err);
}
string encryptedCvc = EncryptCvc(cvc);
if (AlreadyExists(cardnumber))
{
string err = mCulture == mJpLang
? $"カード「{brand}/{holdername} ({expiration})」は既に存在します。"
: $"The card '{brand}/{holdername} ({expiration})' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Cc (Brand, Cardnumber, Expiration, Cvc, Holdername, Note)
VALUES ($brand, $cardnumber, $expiration, $cvc, $holdername, $note)";
com.Parameters.AddWithValue("$brand", brand);
com.Parameters.AddWithValue("$cardnumber", cardnumber);
com.Parameters.AddWithValue("$expiration", expiration);
com.Parameters.AddWithValue("$cvc", encryptedCvc);
com.Parameters.AddWithValue("$holdername", holdername);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditCc(int id, string brand, string cardnumber, string expiration, string cvc, string holdername, string note)
{
if (string.IsNullOrWhiteSpace(brand) || string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(expiration) || string.IsNullOrWhiteSpace(cvc) || string.IsNullOrWhiteSpace(holdername))
{
string err = mCulture == mJpLang
? "ブランド、カード番号、氏名、有効期限及び、CVCを御入力下さい。"
: "Please fill in the brand, card number, full name, expiration, and CVC.";
throw new ArgumentException(err);
}
string encryptedCvc = EncryptCvc(cvc);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Cc
SET Brand = $brand, Cardnumber = $cardnumber, Expiration = $expiration, Cvc = $cvc, Holdername = $holdername, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
com.Parameters.AddWithValue("$brand", brand);
com.Parameters.AddWithValue("$cardnumber", cardnumber);
com.Parameters.AddWithValue("$expiration", expiration);
com.Parameters.AddWithValue("$cvc", encryptedCvc);
com.Parameters.AddWithValue("$holdername", holdername);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteCc(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Cc WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Brand, string Cardnumber, string Expiration, string Cvc, string Holdername, string Note)> GetAll(string keyword = "")
{
var ccs = new List<(int, string, string, string, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Brand, Cardnumber, Expiration, Cvc, Holdername, Note FROM Cc
ORDER BY Brand ASC";
}
else
{
com.CommandText = @"
SELECT Id, Brand, Cardnumber, Expiration, Cvc, Holdername, Note FROM Cc
WHERE Brand LIKE @keyword OR Holdername LIKE @keyword OR Cardnumber LIKE @keyword
ORDER BY Brand ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedCvc = reader.GetString(4);
string decryptedCvc = DecryptCvc(encryptedCvc);
ccs.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
reader.GetString(3),
decryptedCvc,
reader.GetString(5),
reader.IsDBNull(6) ? string.Empty : reader.GetString(6)
));
}
}
}
return ccs;
}
private string EncryptCvc(string answer)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptCvc(string encryptedAnswer)
{
byte[] combined = Convert.FromBase64String(encryptedAnswer);
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 = mEncryptionKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}

231
Managers/CryptoManager.cs Normal file
View File

@@ -0,0 +1,231 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
internal class CryptoManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public CryptoManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string currency, string name, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Crypto
WHERE UPPER(Currency) = UPPER(@currency) AND UPPER(Name) = UPPER(@name)";
com.Parameters.AddWithValue("@currency", currency);
com.Parameters.AddWithValue("@name", name);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddCrypto(string currency, string name, string address, string seed, string viewkey, string spendkey, string height, string password, string note)
{
if (string.IsNullOrWhiteSpace(currency) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(address))
{
string err = mCulture == mJpLang
? "通貨、ウォレット名及び、住所を御入力下さい。"
: "Please fill in the currency, wallet name, and address.";
throw new ArgumentException(err);
}
string encryptedPassword = !string.IsNullOrWhiteSpace(password) ? EncryptInfo(password) : "";
string encryptedSeed = !string.IsNullOrWhiteSpace(seed) ? EncryptInfo(seed) : "";
string encryptedViewkey = !string.IsNullOrWhiteSpace(viewkey) ? EncryptInfo(viewkey) : "";
string encryptedSpendkey = !string.IsNullOrWhiteSpace(spendkey) ? EncryptInfo(spendkey) : "";
if (AlreadyExists(currency, name))
{
string err = mCulture == mJpLang
? $"通貨及びウォレット名「{currency}/{name}」エントリは既に存在します。"
: $"An entry with the currency and wallet name for '{currency}/{name}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Crypto (Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note)
VALUES ($currency, $name, $address, $seed, $viewkey, $spendkey, $height, $password, $note)";
com.Parameters.AddWithValue("$currency", currency);
com.Parameters.AddWithValue("$name", name);
com.Parameters.AddWithValue("$address", address);
com.Parameters.AddWithValue("$seed", string.IsNullOrEmpty(seed) ? DBNull.Value : encryptedSeed);
com.Parameters.AddWithValue("$viewkey", string.IsNullOrEmpty(viewkey) ? DBNull.Value : encryptedViewkey);
com.Parameters.AddWithValue("$spendkey", string.IsNullOrEmpty(spendkey) ? DBNull.Value : encryptedSpendkey);
com.Parameters.AddWithValue("$height", string.IsNullOrEmpty(height) ? DBNull.Value : height);
com.Parameters.AddWithValue("$password", string.IsNullOrEmpty(password) ? DBNull.Value : encryptedPassword);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditCrypto(int id, string currency, string name, string address, string seed, string viewkey, string spendkey, string height, string password, string note)
{
if (string.IsNullOrWhiteSpace(currency) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(address))
{
string err = mCulture == mJpLang
? "通貨、ウォレット名及び、住所を御入力下さい。"
: "Please fill in the currency, wallet name, and address.";
throw new ArgumentException(err);
}
string encryptedPassword = string.IsNullOrEmpty(password) ? string.Empty : EncryptInfo(password);
string encryptedSeed = string.IsNullOrEmpty(seed) ? string.Empty : EncryptInfo(seed);
string encryptedViewkey = string.IsNullOrEmpty(viewkey) ? string.Empty : EncryptInfo(viewkey);
string encryptedSpendkey = string.IsNullOrEmpty(spendkey) ? string.Empty : EncryptInfo(spendkey);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Crypto
SET Currency = $currency, Name = $name, Address = $address, Seed = $seed, Viewkey = $viewkey, Spendkey = $spendkey, Height = $height, Password = $password, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
com.Parameters.AddWithValue("$currency", currency);
com.Parameters.AddWithValue("$name", name);
com.Parameters.AddWithValue("$address", address);
com.Parameters.AddWithValue("$seed", string.IsNullOrEmpty(seed) ? DBNull.Value : encryptedSeed);
com.Parameters.AddWithValue("$viewkey", string.IsNullOrEmpty(viewkey) ? DBNull.Value : encryptedViewkey);
com.Parameters.AddWithValue("$spendkey", string.IsNullOrEmpty(spendkey) ? DBNull.Value : encryptedSpendkey);
com.Parameters.AddWithValue("$height", string.IsNullOrEmpty(height) ? DBNull.Value : height);
com.Parameters.AddWithValue("$password", string.IsNullOrEmpty(password) ? DBNull.Value : encryptedPassword);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteCrypto(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Crypto WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Currency, string Name, string Address, string Seed, string Viewkey, string Spendkey, string Height, string Password, string Note)> GetAll(string keyword = "")
{
var cryptos = new List<(int, string, string, string, string, string, string, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note FROM Crypto
ORDER BY Currency DESC";
}
else
{
com.CommandText = @"
SELECT Id, Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note FROM Crypto
WHERE Currency LIKE @keyword OR Name LIKE @keyword
ORDER BY Currency DESC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string decryptedSeed = DecryptInfo(reader.GetString(4));
string decryptedViewkey = reader.IsDBNull(5) ? string.Empty : DecryptInfo(reader.GetString(5));
string decryptedSpendkey = reader.IsDBNull(6) ? string.Empty : DecryptInfo(reader.GetString(6));
string decryptedPassword = reader.IsDBNull(8) ? string.Empty : DecryptInfo(reader.GetString(8));
cryptos.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
reader.GetString(3),
decryptedSeed,
reader.IsDBNull(5) ? string.Empty : decryptedViewkey,
reader.IsDBNull(6) ? string.Empty : decryptedSpendkey,
reader.IsDBNull(7) ? string.Empty : reader.GetString(7),
reader.IsDBNull(8) ? string.Empty : decryptedPassword,
reader.IsDBNull(9) ? string.Empty : reader.GetString(9)
));
}
}
}
return cryptos;
}
private string EncryptInfo(string answer)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptInfo(string encryptedAnswer)
{
byte[] combined = Convert.FromBase64String(encryptedAnswer);
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 = mEncryptionKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Security.Cryptography;
namespace SimPas2_Windows.Managers
{
public class GeneratorManager
{
public GeneratorManager()
{
}
public string GeneratePassword(bool isSecure, int length)
{
const string charset_risky = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const string charset_secure = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()=~-^\\|_@`[{]};:+*<>,./?";
string charset = isSecure ? charset_secure : charset_risky;
byte[] random = RandomNumberGenerator.GetBytes(length);
char[] res = new char[length];
for (int i = 0; i < length; i++)
{
res[i] = charset[random[i] % charset.Length];
}
return new string(res);
}
}
}

172
Managers/NoteManager.cs Normal file
View File

@@ -0,0 +1,172 @@
using Microsoft.Data.Sqlite;
namespace SimPas2_Windows.Managers
{
public class NoteManager
{
private readonly string mConnectionString;
private readonly string mCulture;
private string mJpLang;
public NoteManager(string databasePath, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string name, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Notes
WHERE UPPER(Name) = UPPER(@name)";
com.Parameters.AddWithValue("@name", name);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddNote(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
string err = mCulture == mJpLang
? "ファイル名を御入力下さい。"
: "Please fill in the filename.";
throw new ArgumentException(err);
}
if (AlreadyExists(name))
{
string err = mCulture == mJpLang
? $"ファイル名「{name}」付きメモは既に存在します。"
: $"A note with the filename '{name}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Notes (Name)
VALUES ($name)";
com.Parameters.AddWithValue("$name", name);
com.ExecuteNonQuery();
}
}
public bool EditNote(int id, string name)
{
if (string.IsNullOrWhiteSpace(name))
{
string err = mCulture == mJpLang
? "ファイル名を御入力下さい。" :
"Please fill in the filename.";
throw new ArgumentException(err);
}
if (AlreadyExists(name, id))
{
string err = mCulture == mJpLang
? $"ファイル名「{name}」付きメモは既に存在します。"
: $"A note with the filename '{name}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Notes
SET Name = $name
WHERE Id = $id";
com.Parameters.AddWithValue("id", id);
com.Parameters.AddWithValue("$name", name);
return com.ExecuteNonQuery() > 0;
}
}
public bool SaveNote(int id, string text)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Notes
SET Text = @text
WHERE Id = @id";
com.Parameters.AddWithValue("@id", id);
com.Parameters.AddWithValue("@text", text ?? (object)DBNull.Value);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteNote(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Notes WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Name, string Text)> GetAll(string keyword = "")
{
var notes = new List<(int, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Name, Text FROM Notes
ORDER BY Name ASC";
}
else
{
com.CommandText = @"
SELECT Id, Name, Text FROM Notes
WHERE Name LIKE @keyword
ORDER BY Name ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
notes.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.IsDBNull(2) ? string.Empty : reader.GetString(2)
));
}
}
}
return notes;
}
}
}

479
Managers/OtpManager.cs Normal file
View File

@@ -0,0 +1,479 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
public class OtpManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public OtpManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string website, string secret, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Otp
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Secret) = UPPER(@secret)";
com.Parameters.AddWithValue("@website", website);
com.Parameters.AddWithValue("@secret", secret);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddOtp(string website, string secret, string issuer, string algorithm, int duration, int digits, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(secret) || string.IsNullOrWhiteSpace(issuer))
{
string err = mCulture == mJpLang
? "ウェブサイト、秘密鍵及び、発行者を御入力下さい。"
: "Please fill in the website, secret, and issuer.";
throw new ArgumentException(err);
}
string encryptedSecret = EncryptSecret(secret);
if (AlreadyExists(website, encryptedSecret))
{
string err = mCulture == mJpLang
? $"ウェブサイト「{website}」向け秘密鍵は既に存在します。"
: $"An OTP with the website and secret for '{website}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Otp (Website, Secret, Issuer, Algorithm, Duration, Digits, Note)
VALUES ($website, $secret, $issuer, $algorithm, $duration, $digits, $note)";
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$secret", encryptedSecret);
com.Parameters.AddWithValue("$issuer", issuer);
com.Parameters.AddWithValue("$algorithm", algorithm);
com.Parameters.AddWithValue("$duration", duration);
com.Parameters.AddWithValue("$digits", digits);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditOtp(int id, string website, string secret, string issuer, string algorithm, int duration, int digits, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(secret) || string.IsNullOrWhiteSpace(issuer))
{
string err = mCulture == mJpLang
? "ウェブサイト、秘密鍵及び、発行者を御入力下さい。"
: "Please fill in the website, secret, and issuer.";
throw new ArgumentException(err);
}
string encryptedSecret = EncryptSecret(secret);
if (AlreadyExists(website, encryptedSecret, id))
{
string err = mCulture == mJpLang
? $"ウェブサイト「{website}」向け秘密鍵は既に存在します。"
: $"An OTP with the website and secret for '{website}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Otp
SET Website = $website, Secret = $secret, Issuer = $issuer, Algorithm = $algorithm, Duration = $duration, Digits = $digits, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$secret", encryptedSecret);
com.Parameters.AddWithValue("$issuer", issuer);
com.Parameters.AddWithValue("$algorithm", algorithm);
com.Parameters.AddWithValue("$duration", duration);
com.Parameters.AddWithValue("$digits", digits);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteOtp(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Otp WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Website, string Secret, string Issuer, string Algorithm, int Duration, int Digits, string Note)> GetAll(string keyword = "")
{
var otps = new List<(int, string, string, string, string, int, int, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Website, Secret, Issuer, Algorithm, Duration, Digits, Note FROM Otp
ORDER BY Website ASC";
}
else
{
com.CommandText = @"
SELECT Id, Website, Secret, Issuer, Algorithm, Duration, Digits, Note FROM Otp
WHERE Website LIKE @keyword OR Issuer LIKE @keyword
ORDER BY Website ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedSecret = reader.GetString(2);
string decryptedSecret = DecryptSecret(encryptedSecret);
otps.Add((
reader.GetInt32(0),
reader.GetString(1),
decryptedSecret,
reader.GetString(3),
reader.GetString(4),
reader.GetInt32(5),
reader.GetInt32(6),
reader.IsDBNull(7) ? string.Empty : reader.GetString(7)
));
}
}
}
return otps;
}
public (string Code, string Error) GenerateTotp(string secret, int digits, string algorithm, int duration = 30)
{
try
{
var (sec, error) = ExtractSecret(secret);
if (!string.IsNullOrEmpty(error))
{
return ("", error);
}
long currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
ulong counter = (ulong)(currentTime / duration);
uint totp = GenerateTotpCode(sec, counter, digits, algorithm);
return (totp.ToString($"D{digits}"), "");
}
catch (Exception ex)
{
return ("", $"TOTP generation error: {ex.Message}");
}
}
private (byte[]? Secret, string Error) ExtractSecret(string otpauthUrl)
{
if (string.IsNullOrEmpty(otpauthUrl))
{
string err = mCulture == mJpLang
? "OTP Authを御入力下さい。"
: "Please fill in the OTP Auth.";
throw new ArgumentException(err);
}
if (!otpauthUrl.StartsWith("otpauth://totp/", StringComparison.OrdinalIgnoreCase))
{
try
{
byte[] secretDecoded = Base32Decode(otpauthUrl);
return (secretDecoded, "");
}
catch (Exception ex)
{
string err = mCulture == mJpLang
? $"TOTP秘密鍵を復号化に失敗{ex.Message}"
: $"Failed to decode TOTP secret: {ex.Message}";
return (null, err);
}
}
int secretStart = otpauthUrl.IndexOf("secret=", StringComparison.OrdinalIgnoreCase);
if (secretStart == -1)
{
string err = mCulture == mJpLang
? "TOTP秘密鍵をURLの中に見つけられませんでした。"
: "TOTP secret not found in URL";
return (null, err);
}
secretStart += 7;
int secretEnd = otpauthUrl.IndexOf('&', secretStart);
if (secretEnd == -1)
{
secretEnd = otpauthUrl.Length;
}
if (secretEnd <= secretStart)
{
string err = mCulture == mJpLang
? "不正なTOTP秘密枠"
: "Invalid TOTP secret range";
return (null, err);
}
string secretEncoded = otpauthUrl.Substring(secretStart, secretEnd - secretStart);
try
{
byte[] secretDecoded = Base32Decode(secretEncoded);
return (secretDecoded, "");
}
catch (Exception ex)
{
string err = mCulture == mJpLang
? $"TOTP秘密鍵を復号化に失敗{ex.Message}"
: $"Failed to decode TOTP secret: {ex.Message}";
return (null, err);
}
}
public (string Secret, string Issuer, string Algorithm, int Duration, int Digits) ParseOtpAuthUrl(string otpAuthUrl)
{
if (string.IsNullOrEmpty(otpAuthUrl) || !otpAuthUrl.StartsWith("otpauth://totp/", StringComparison.OrdinalIgnoreCase))
{
string err = mCulture == mJpLang
? "不正なOTP Auth URL"
: "Invalid OTP Auth URL";
throw new ArgumentException(err);
}
string issuer = "";
string algorithm = "SHA1";
int duration = 30;
int digits = 6;
int labelStart = "otpauth://totp/".Length;
int labelEnd = otpAuthUrl.IndexOf('?', labelStart);
if (labelEnd != -1)
{
issuer = otpAuthUrl.Substring(labelStart, labelEnd - labelStart);
if (issuer.Contains(':'))
{
issuer = issuer.Substring(0, issuer.IndexOf(':'));
}
}
int secretStart = otpAuthUrl.IndexOf("secret=", StringComparison.OrdinalIgnoreCase);
if (secretStart == -1)
{
string err = mCulture == mJpLang
? "TOTP秘密鍵をURLの中に見つけられませんでした。"
: "TOTP secret not found in URL";
throw new ArgumentException(err);
}
secretStart += 7;
int secretEnd = otpAuthUrl.IndexOf('&', secretStart);
if (secretEnd == -1)
{
secretEnd = otpAuthUrl.Length;
}
string secret = otpAuthUrl.Substring(secretStart, secretEnd - secretStart);
var queryParams = otpAuthUrl.Substring(labelEnd + 1).Split('&').Select(p => p.Split('=')).ToDictionary(p => p[0].ToLower(), p => p.Length > 1 ? p[1] : "");
if (queryParams.ContainsKey("issuer"))
{
issuer = queryParams["issuer"];
}
if (queryParams.ContainsKey("algorithm"))
{
algorithm = queryParams["algorithm"].ToUpper();
if (algorithm != "SHA1" && algorithm != "SHA256" && algorithm != "SHA512")
{
algorithm = "SHA1";
}
}
if (queryParams.ContainsKey("period") && int.TryParse(queryParams["period"], out int parsedPeriod))
{
duration = parsedPeriod;
}
if (queryParams.ContainsKey("digits") && int.TryParse(queryParams["digits"], out int parsedDigits))
{
digits = parsedDigits;
}
return (secret, issuer, algorithm, duration, digits);
}
private uint GenerateTotpCode(byte[] secret, ulong counter, int digits, string algorithm)
{
byte[] counterBytes = BitConverter.GetBytes(counter);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(counterBytes);
}
using (HMAC hmac = algorithm.ToUpper() switch
{
"SHA256" => new HMACSHA256(secret),
"SHA512" => new HMACSHA512(secret),
_ => new HMACSHA1(secret),
})
{
byte[] hash = hmac.ComputeHash(counterBytes);
int offset = hash[hash.Length - 1] & 0x0F;
uint truncatedHash =
(uint)(hash[offset] & 0x7F) << 24 |
(uint)(hash[offset + 1] & 0xFF) << 16 |
(uint)(hash[offset + 2] & 0xFF) << 8 |
(uint)(hash[offset + 3] & 0xFF);
return truncatedHash % (uint)Math.Pow(10, digits);
}
}
private byte[] Base32Decode(string base32)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
if (string.IsNullOrEmpty(base32))
{
string err = mCulture == mJpLang
? "Base32文字はnullや空です。"
: "Base32 string cannot be null or empty";
throw new ArgumentException(err);
}
// Remove spaces, hyphens, and convert to uppercase
base32 = base32.Trim().Replace(" ", "").Replace("-", "").ToUpper();
// Count padding characters
int padding = 0;
for (int i = base32.Length - 1; i >= 0 && base32[i] == '='; i--)
{
padding++;
}
// Remove padding for processing
string cleanBase32 = base32.Substring(0, base32.Length - padding);
if (cleanBase32.Any(c => !alphabet.Contains(c)))
{
string err = mCulture == mJpLang
? "Base32文字は不正文字が含みます。"
: "Base32 string contains invalid characters";
throw new ArgumentException(err);
}
// Calculate output length
int byteCount = cleanBase32.Length * 5 / 8;
if (byteCount == 0)
{
return Array.Empty<byte>();
}
byte[] buffer = new byte[byteCount];
int bufferIndex = 0;
int bits = 0;
int bitCount = 0;
foreach (char c in cleanBase32)
{
int value = alphabet.IndexOf(c);
if (value < 0)
{
string err = mCulture == mJpLang
? "Base32文字は不正文字が含みます。"
: "Base32 string contains invalid characters";
throw new ArgumentException(err);
}
bits = bits << 5 | value;
bitCount += 5;
if (bitCount >= 8)
{
buffer[bufferIndex++] = (byte)(bits >> bitCount - 8);
bitCount -= 8;
}
}
// Resize to actual bytes written
if (bufferIndex < byteCount)
{
Array.Resize(ref buffer, bufferIndex);
}
return buffer;
}
private string EncryptSecret(string secret)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(secret);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptSecret(string encryptedSecret)
{
byte[] combined = Convert.FromBase64String(encryptedSecret);
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 = mEncryptionKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}

214
Managers/PasswordManager.cs Normal file
View File

@@ -0,0 +1,214 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
public class PasswordManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public PasswordManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string website, string username, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Passwords
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Username) = UPPER(@username)";
com.Parameters.AddWithValue("@website", website);
com.Parameters.AddWithValue("@username", username);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddPassword(string website, string username, string password, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
string err = mCulture == mJpLang
? "ウェブサイト、ユーザー名及び、パスワードを御入力下さい。"
: "Please fill in the website, user/email, and password.";
throw new ArgumentException(err);
}
if (AlreadyExists(website, username))
{
string err = mCulture == mJpLang
? $"ウェブサイトとユーザー名「{website}/{username}」付きパスワードは既に存在します。"
: $"A password with the website and user/email of '{website}/{username}' already exists.";
throw new ArgumentException(err);
}
string encryptedPassword = EncryptPassword(password);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Passwords (Website, Username, Password, Note)
VALUES ($website, $username, $password, $note)";
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$username", username);
com.Parameters.AddWithValue("$password", encryptedPassword);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditPassword(int id, string website, string username, string password, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
string err = mCulture == "ja-JP"
? "ウェブサイト、ユーザー名及び、パスワードを御入力下さい。"
: "Please fill in the website, user/email, and password.";
throw new ArgumentException(err);
}
if (AlreadyExists(website, username, id))
{
string err = mCulture == mJpLang
? $"ウェブサイトとユーザー名「{website}/{username}」付きパスワードは既に存在します。"
: $"A password with the website and user/email of '{website}/{username}' already exists.";
throw new ArgumentException(err);
}
string encryptedPassword = EncryptPassword(password);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Passwords
SET Website = $website, Username = $username, Password = $password, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("id", id);
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$username", username);
com.Parameters.AddWithValue("$password", encryptedPassword);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeletePassword(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Passwords WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Website, string Username, string Password, string Note)> GetAll(string keyword = "")
{
var passwords = new List<(int, string, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Website, Username, Password, Note FROM Passwords
ORDER BY Website ASC";
}
else
{
com.CommandText = @"
SELECT Id, Website, Username, Password, Note FROM Passwords
WHERE Website LIKE @keyword OR Username LIKE @keyword
ORDER BY Website ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedPassword = reader.GetString(3);
string decryptedPassword = DecryptPassword(encryptedPassword);
passwords.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
decryptedPassword,
reader.IsDBNull(4) ? string.Empty : reader.GetString(4)
));
}
}
}
return passwords;
}
private string EncryptPassword(string plainPassword)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (var encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainPassword);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
// Combine IV and encrypted data for storage
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptPassword(string encryptedPassword)
{
byte[] combined = Convert.FromBase64String(encryptedPassword);
byte[] iv = new byte[16]; // AES IV is 16 bytes
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 = mEncryptionKey;
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}

208
Managers/PinManager.cs Normal file
View File

@@ -0,0 +1,208 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
public class PinManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public PinManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string website, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Pin
WHERE UPPER(Website) = UPPER(@website)";
com.Parameters.AddWithValue("@website", website);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddPin(string website, string pincode, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(pincode))
{
string err = mCulture == mJpLang
? "ウェブサイト及び暗証番号を御入力下さい。"
: "Please fill in the website and pincode.";
throw new ArgumentException(err);
}
if (AlreadyExists(website))
{
string err = mCulture == mJpLang
? $"ウェブサイト「{website}」付き暗証番号は既に存在します。"
: $"A pincode with the website '{website}' already exists.";
throw new ArgumentException(err);
}
string encryptedPin = EncryptPin(pincode);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"INSERT INTO Pin (Website, Pincode, Note) VALUES ($website, $pincode, $note)";
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$pincode", encryptedPin);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditPin(int id, string website, string pincode, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(pincode))
{
string err = mCulture == mJpLang
? "ウェブサイト及び暗証番号を御入力下さい。"
: "Please fill in the website and pincode.";
throw new ArgumentException(err);
}
if (AlreadyExists(website, id))
{
string err = mCulture == mJpLang
? $"ウェブサイト「{website}」付き暗証番号は既に存在します。"
: $"A pincode with the website '{website}' already exists.";
throw new ArgumentException(err);
}
string encryptedPin = EncryptPin(pincode);
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"UPDATE Pin SET Website = $website, Pincode = $pincode, Note = $note WHERE Id = $id";
com.Parameters.AddWithValue("id", id);
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$pincode", encryptedPin);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeletePin(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Pin WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Website, string Pincode, string Note)> GetAll(string keyword = "")
{
var pins = new List<(int, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Website, Pincode, Note FROM Pin
ORDER BY Website ASC";
}
else
{
com.CommandText = @"
SELECT Id, Website, Pincode, Note FROM Pin
WHERE Website LIKE @keyword
ORDER BY Website ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedPin = reader.GetString(2);
string decryptedPin = DecryptPin(encryptedPin);
pins.Add((
reader.GetInt32(0),
reader.GetString(1),
decryptedPin,
reader.IsDBNull(3) ? string.Empty : reader.GetString(3)
));
}
}
}
return pins;
}
private string EncryptPin(string plainPassword)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (var encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainPassword);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptPin(string encryptedPassword)
{
byte[] combined = Convert.FromBase64String(encryptedPassword);
byte[] iv = new byte[16]; // AES IV is 16 bytes
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 = mEncryptionKey;
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}

215
Managers/QaManager.cs Normal file
View File

@@ -0,0 +1,215 @@
using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
public class QaManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public QaManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string website, string question, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Qa
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Question) = UPPER(@question)";
com.Parameters.AddWithValue("@website", website);
com.Parameters.AddWithValue("@question", question);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddQa(string website, string question, string answer, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
{
string err = mCulture == mJpLang
? "ウェブサイト、質問及び、回答を御入力下さい。"
: "Please fill in the website, question, and answer.";
throw new ArgumentException(err);
}
string encryptedAnswer = EncryptAnswer(answer);
if (AlreadyExists(website, question))
{
string err = mCulture == mJpLang
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
: $"An secret question with the website and question for '{website}/{question}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Qa (Website, Question, Answer, Note)
VALUES ($website, $question, $answer, $note)";
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$question", question);
com.Parameters.AddWithValue("$answer", encryptedAnswer);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditQa(int id, string website, string question, string answer, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
{
string err = mCulture == mJpLang
? "ウェブサイト、質問及び、回答を御入力下さい。"
: "Please fill in the website, question, and answer.";
throw new ArgumentException(err);
}
string encryptedAnswer = EncryptAnswer(answer);
if (AlreadyExists(website, question, id))
{
string err = mCulture == mJpLang
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
: $"An secret question with the website and question for '{website}/{question}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Qa
SET Website = $website, Question = $question, Answer = $answer, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$question", question);
com.Parameters.AddWithValue("$answer", encryptedAnswer);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteQa(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Qa WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Website, string Question, string Answer, string Note)> GetAll(string keyword = "")
{
var qas = new List<(int, string, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Website, Question, Answer, Note FROM Qa
ORDER BY Website ASC";
}
else
{
com.CommandText = @"
SELECT Id, Website, Question, Answer, Note FROM Qa
WHERE Website LIKE @keyword OR Question LIKE @keyword
ORDER BY Website ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedAnswer = reader.GetString(3);
string decryptedAnswer = DecryptAnswer(encryptedAnswer);
qas.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
decryptedAnswer,
reader.IsDBNull(4) ? string.Empty : reader.GetString(4)
));
}
}
}
return qas;
}
private string EncryptAnswer(string answer)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptAnswer(string encryptedAnswer)
{
byte[] combined = Convert.FromBase64String(encryptedAnswer);
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 = mEncryptionKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}