Last active
July 16, 2022 12:03
-
-
Save 7shi/c1ddcbf50ea5e5400e6e8c8924cae9ba to your computer and use it in GitHub Desktop.
Crypto Notepad with Rijndael
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (0) PUBLIC DOMAIN | |
// To the extent possible under law, the person who associated CC0 with this work | |
// has waived all copyright and related or neighboring rights to this work. | |
#r "System" | |
#if NETF | |
#r "System.Drawing" | |
#r "System.Windows.Forms" | |
#else | |
//#r "nuget: System.Drawing.Common, 5.0.2" | |
#r @"C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.5\System.Drawing.Common.dll" | |
#r @"C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.5\System.Windows.Forms.dll" | |
#r @"C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.5\System.Windows.Forms.Primitives.dll" | |
#r @"C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.5\Microsoft.Win32.SystemEvents.dll" | |
#endif | |
open System | |
open System.IO | |
open System.IO.Compression | |
open System.Text | |
open System.Drawing | |
open System.Reflection | |
open System.Security.Cryptography | |
open System.Windows.Forms | |
let encrypt (sout: Stream) (data: byte[]) (password: string) = | |
use key = new Rfc2898DeriveBytes(password, 16) | |
sout.Write(key.Salt, 0, 16) | |
use aes = Aes.Create() | |
aes.Key <- key.GetBytes 16 | |
aes.GenerateIV() | |
sout.Write(aes.IV, 0, 16) | |
let enc = aes.CreateEncryptor() | |
use cse = new CryptoStream(sout, enc, CryptoStreamMode.Write) | |
use ds = new DeflateStream(cse, CompressionMode.Compress) | |
ds.Write(data, 0, data.Length) | |
let decrypt (sout: Stream) (data: byte[]) (password: string) = | |
use ms = new MemoryStream(data) | |
use br = new BinaryReader(ms) | |
let salt = br.ReadBytes 16 | |
use key = new Rfc2898DeriveBytes(password, salt) | |
use aes = Aes.Create() | |
aes.Key <- key.GetBytes 16 | |
aes.IV <- br.ReadBytes 16 | |
let dec = aes.CreateDecryptor() | |
use cse = new CryptoStream(ms, dec, CryptoStreamMode.Read) | |
use ds = new DeflateStream(cse, CompressionMode.Decompress) | |
let buf = Array.zeroCreate<byte> 4096 | |
let mutable len = 1 | |
while len > 0 do | |
len <- ds.Read(buf, 0, buf.Length) | |
sout.Write(buf, 0, len) | |
type PasswordDialog() as this = | |
inherit Form( | |
Text = "Password", ClientSize = Size(225, 71), FormBorderStyle = FormBorderStyle.FixedDialog, | |
MaximizeBox = false, MinimizeBox = false, ShowIcon = false, ShowInTaskbar = false, | |
StartPosition = FormStartPosition.CenterParent) | |
let textBox1 = new TextBox(Location = Point(12, 12), PasswordChar = '*', Size = Size(201, 19), TabIndex = 0) | |
let button1 = new Button(Text = "OK", DialogResult = DialogResult.OK, Location = Point(57, 37), Size = Size(75, 23), TabIndex = 1) | |
let button2 = new Button(Text = "Cancel", DialogResult = DialogResult.Cancel, Location = Point(138, 37), Size = Size(75, 23), TabIndex = 2) | |
do | |
this.AcceptButton <- button1 | |
this.CancelButton <- button2 | |
this.Controls.Add textBox1 | |
this.Controls.Add button1 | |
this.Controls.Add button2 | |
textBox1.KeyPress.Add <| fun e -> | |
match e.KeyChar |> int |> enum<Keys> with | |
| Keys.Enter -> | |
button1.PerformClick() | |
e.Handled <- true | |
| _ -> () | |
member _.Password = textBox1.Text | |
//let font = new Font("MS ゴシック", Control.DefaultFont.Size) | |
let font = new Font(FontFamily.GenericMonospace, Control.DefaultFont.Size) | |
let filter = "暗号化テキスト (*.tx!)|*.tx!|すべてのファイル (*.*)|*.*" | |
type Form1() as this = | |
inherit Form(ClientSize = Size(500, 450)) | |
let tb = | |
new TextBox( | |
AcceptsReturn = true, AcceptsTab = true, AllowDrop = true, Multiline = true, WordWrap = false, | |
HideSelection = false, Dock = DockStyle.Fill, ScrollBars = ScrollBars.Both, Font = font) | |
let tv = new TreeView( | |
Indent = 4, | |
//ShowRootLines = false, | |
ShowLines = false, | |
FullRowSelect = true, | |
HideSelection = false, | |
Dock = DockStyle.Fill) | |
let mutable isChanged = false | |
let mutable filename = "" | |
let mutable password = "" | |
let focus() = | |
Action(fun _ -> | |
tb.Focus() |> ignore | |
tb.DeselectAll()) | |
|> tb.BeginInvoke | |
|> ignore | |
let parse() = | |
tv.Nodes.Clear() | |
let lines = tb.Lines | |
for i = 0 to lines.Length - 1 do | |
let line = lines.[i] | |
if line.StartsWith "* " then | |
tv.Nodes.Add(new TreeNode(line.[2..], Tag = i)) |> ignore | |
let getPassword() = | |
use f = new PasswordDialog() | |
if f.ShowDialog this = DialogResult.OK then f.Password else "" | |
let openFile (fileName) = | |
let data = | |
try File.ReadAllBytes fileName |> Some | |
with e -> | |
MessageBox.Show( | |
e.Message, this.Text, | |
MessageBoxButtons.OK, MessageBoxIcon.Exclamation) |> ignore | |
None | |
if data.IsNone then () else | |
let f() = | |
let pwd = getPassword() | |
if String.IsNullOrEmpty pwd then false else | |
try | |
#if COMPAT | |
let bytes = | |
try | |
use ms = new MemoryStream() | |
decrypt2 ms data.Value pwd | |
ms.ToArray() | |
with _ -> | |
use ms = new MemoryStream() | |
decrypt ms data.Value (pwd |> Encoding.UTF8.GetBytes |> md5.ComputeHash) | |
ms.ToArray() | |
#else | |
use ms = new MemoryStream() | |
decrypt ms data.Value pwd | |
let bytes = ms.ToArray() | |
#endif | |
tb.Clear() | |
tb.Text <- Encoding.UTF8.GetString bytes | |
focus() | |
parse() | |
isChanged <- false | |
filename <- fileName | |
password <- pwd | |
false | |
with _ -> | |
MessageBox.Show( | |
"パスワードが間違っています。", this.Text, | |
MessageBoxButtons.OK, MessageBoxIcon.Exclamation) |> ignore | |
true | |
while f() do () | |
let saveFile() = | |
parse() | |
if String.IsNullOrEmpty filename then false else | |
if String.IsNullOrEmpty password then password <- getPassword() | |
if String.IsNullOrEmpty password then false else | |
try | |
use ms = new MemoryStream() | |
encrypt ms (Encoding.UTF8.GetBytes tb.Text) password | |
let data = ms.ToArray() | |
use fs = new FileStream(filename, FileMode.Create) | |
fs.Write(data, 0, data.Length) | |
isChanged <- false | |
true | |
with ex -> | |
MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) |> ignore | |
false | |
let saveAs() = | |
use saveFileDialog1 = new SaveFileDialog(Filter = filter) | |
if not <| String.IsNullOrEmpty filename then | |
saveFileDialog1.InitialDirectory <- Path.GetDirectoryName filename | |
saveFileDialog1.FileName <- Path.GetFileName filename | |
if saveFileDialog1.ShowDialog(this) <> DialogResult.OK then false else | |
filename <- saveFileDialog1.FileName; | |
password <- "" | |
saveFile() | |
let save() = | |
if String.IsNullOrEmpty filename then saveAs() else saveFile() | |
let checkSave() = | |
if not isChanged then true else | |
let result = | |
MessageBox.Show( | |
this, "変更を保存しますか?", this.Text, | |
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) | |
match result with | |
| DialogResult.Cancel -> false | |
| DialogResult.No -> true | |
| _ -> save() | |
let find target = | |
if String.IsNullOrEmpty target then () else | |
let s = tb.SelectionStart + tb.SelectionLength | |
let i = tb.Text.IndexOf(target, s) | |
let i = if i < 0 then tb.Text.IndexOf target else i | |
if i < 0 then () else | |
tb.SelectionStart <- i | |
tb.SelectionLength <- target.Length | |
tb.ScrollToCaret() | |
let moveLine line = | |
if line < 0 then () else | |
let i = tb.GetFirstCharIndexFromLine line | |
if i < 0 then () else | |
tb.Focus() |> ignore | |
let lines = tb.Lines | |
tb.SelectionLength <- 0 | |
//src.SelectionStart <- src.GetFirstCharIndexFromLine (max (line - 10) 0) | |
tb.SelectionStart <- tb.GetFirstCharIndexFromLine (min (line + 10) (lines.Length - 1)) | |
tb.ScrollToCaret() | |
tb.SelectionStart <- i | |
tb.SelectionLength <- lines.[line].Length | |
tb.ScrollToCaret() | |
do | |
let menuStrip = new MenuStrip() | |
let miFile = new ToolStripMenuItem("ファイル(&F)") | |
let miFileNew = new ToolStripMenuItem("新規(&N)", ShortcutKeys = (Keys.Control ||| Keys.N), ShowShortcutKeys = true) | |
ignore <| miFile.DropDownItems.Add miFileNew | |
let miFileOpen = new ToolStripMenuItem("開く(&O)", ShortcutKeys = (Keys.Control ||| Keys.O), ShowShortcutKeys = true) | |
ignore <| miFile.DropDownItems.Add miFileOpen | |
let miFileSave = new ToolStripMenuItem("上書き保存(&S)", ShortcutKeys = (Keys.Control ||| Keys.S), ShowShortcutKeys = true) | |
ignore <| miFile.DropDownItems.Add miFileSave | |
let miFileSaveAs = new ToolStripMenuItem("名前を付けて保存(&A)") | |
ignore <| miFile.DropDownItems.Add miFileSaveAs | |
ignore <| miFile.DropDownItems.Add (new ToolStripSeparator()) | |
let miFileExit = new ToolStripMenuItem("終了(&X)") | |
ignore <| miFile.DropDownItems.Add miFileExit | |
ignore <| menuStrip.Items.Add miFile | |
let miEdit = new ToolStripMenuItem("編集(&E)") | |
let miEditFind = new ToolStripMenuItem("検索(&F)", ShortcutKeys = (Keys.Control ||| Keys.F), ShowShortcutKeys = true) | |
ignore <| miEdit.DropDownItems.Add miEditFind | |
ignore <| miEdit.DropDownItems.Add (new ToolStripSeparator()) | |
let miEditPassword = new ToolStripMenuItem("パスワード生成(&P)") | |
ignore <| miEdit.DropDownItems.Add miEditPassword | |
ignore <| menuStrip.Items.Add miEdit | |
let miHelp = new ToolStripMenuItem("ヘルプ(&H)") | |
let miHelpAbout = new ToolStripMenuItem("バージョン情報(&A)") | |
ignore <| miHelp.DropDownItems.Add miHelpAbout | |
ignore <| menuStrip.Items.Add miHelp | |
let pnlFind = new Panel(Dock = DockStyle.Top) | |
let txtFind = new TextBox(Dock = DockStyle.Fill, Font = font) | |
let btnFind = new Button(Text = "検索", AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, Dock = DockStyle.Right) | |
pnlFind.Controls.AddRange [|txtFind; btnFind|] | |
pnlFind.Height <- txtFind.Height | |
let sp = new SplitContainer( | |
Orientation = Orientation.Vertical, | |
Dock = DockStyle.Fill) | |
sp.Panel1.Controls.Add tv | |
sp.Panel2.Controls.Add tb | |
let status = new StatusStrip(Dock = DockStyle.Bottom, Stretch = true) | |
this.Controls.AddRange [|sp; pnlFind; status; menuStrip|] | |
this.MainMenuStrip <- menuStrip | |
this.Text <- "RijndaelPad" | |
tb.TextChanged.Add <| fun _ -> isChanged <- true | |
txtFind.KeyPress.Add <| fun e -> | |
match e.KeyChar |> int |> enum<Keys> with | |
| Keys.Enter -> | |
find txtFind.Text | |
e.Handled <- true | |
| Keys.Escape -> | |
tb.Focus() |> ignore | |
e.Handled <- true | |
| _ -> () | |
btnFind.Click.Add <| fun _ -> find txtFind.Text | |
miFileNew.Click.Add <| fun _ -> | |
if not <| checkSave() then () else | |
isChanged <- false; | |
filename <- "" | |
password <- "" | |
tv.Nodes.Clear() | |
tb.Clear() | |
miFileOpen.Click.Add <| fun _ -> | |
use openFileDialog1 = new OpenFileDialog(Filter = filter) | |
if checkSave() && openFileDialog1.ShowDialog this = DialogResult.OK then | |
this.Open openFileDialog1.FileName | |
miFileSave.Click.Add <| fun _ -> save() |> ignore | |
miFileSaveAs.Click.Add <| fun _ -> saveAs() |> ignore | |
miFileExit.Click.Add <| fun _ -> this.Close() | |
miEditFind.Click.Add <| fun _ -> | |
if tb.SelectionLength > 0 then | |
txtFind.Text <- tb.SelectedText | |
txtFind.Focus() |> ignore | |
txtFind.SelectAll() | |
miEditPassword.Click.Add <| fun _ -> | |
let pwd = | |
seq { | |
let r = new Random() | |
for _ in 0 .. 11 do | |
let v = r.Next(62) | |
yield | |
if v < 26 then int 'A' + v | |
elif v < 52 then int 'a' + (v - 26) | |
else int '0' + (v - 52) } | |
|> Seq.map char | |
|> Seq.toArray | |
|> String | |
tb.SelectedText <- pwd + Environment.NewLine | |
miHelpAbout.Click.Add <| fun _ -> | |
MessageBox.Show("Auto Click Version " + Application.ProductVersion, "バージョン情報") |> ignore | |
tb.DragEnter.Add <| fun e -> | |
if e.Data.GetDataPresent DataFormats.FileDrop then | |
e.Effect <- DragDropEffects.Link | |
tb.DragDrop.Add <| fun e -> | |
let files = e.Data.GetData DataFormats.FileDrop :?> string[] | |
if not <| isNull files && files.Length > 0 then this.Open(files.[0]) | |
tv.NodeMouseClick.Add <| fun e -> | |
match e.Node.Tag with | |
| :? int as line -> tv.BeginInvoke(Action(fun _ -> moveLine line)) |> ignore | |
| _ -> () | |
member _.Open fileName = openFile fileName | |
override _.OnClosing e = | |
base.OnClosing e | |
if not e.Cancel && not <| checkSave() then e.Cancel <- true | |
override _.OnLoad e = | |
base.OnLoad e | |
focus() | |
[<assembly: AssemblyVersion("2.0.*")>] | |
[<EntryPoint; STAThread>] | |
do | |
Application.EnableVisualStyles() | |
Application.SetCompatibleTextRenderingDefault false | |
let f = new Form1() | |
let args = Environment.GetCommandLineArgs() | |
if args.Length > 1 then | |
f.Show() | |
f.Open args.[1] | |
Application.Run f |
暗号化の要点
- AES 暗号化には鍵と初期化ベクタが必要。
- RFC 2898 ではパスワードにソルトを加えて鍵を作る。
- ソルトと初期化ベクタはランダムに生成する。
- 同じデータを同じパスワードで暗号化しても、毎回異なるデータが生成される。
- 複数のファイルを同一のパスワードで暗号化しても、ヒントとなり得るような特徴が生成されにくくなる。
- 復号にはパスワードとソルトと初期化ベクタが必要。
- 単一ファイルで完結させるため、ソルトと初期化ベクタはファイルの先頭に置く。
- 秘匿性はパスワードだけで担保される。
- ソルトと初期化ベクタはランダムに生成されたため、パスワードの特徴は含まない。
- 単一ファイルという縛りがなければ、別の場所に置くなど分離することでより解読は困難になる。
- 単一ファイルで完結させるため、ソルトと初期化ベクタはファイルの先頭に置く。
- 暗号化前に圧縮することで冗長性を下げる。
- 正確に復号できなければ、圧縮を解除することはできない。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
元となった C# 版(暗号化の部分を改良)