Skip to content

Instantly share code, notes, and snippets.

@7shi
Last active July 16, 2022 12:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 7shi/c1ddcbf50ea5e5400e6e8c8924cae9ba to your computer and use it in GitHub Desktop.
Save 7shi/c1ddcbf50ea5e5400e6e8c8924cae9ba to your computer and use it in GitHub Desktop.
Crypto Notepad with Rijndael
// (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
@7shi
Copy link
Author

7shi commented Jul 15, 2022

元となった C# 版(暗号化の部分を改良)

@7shi
Copy link
Author

7shi commented Jul 15, 2022

@7shi
Copy link
Author

7shi commented Jul 16, 2022

暗号化の要点

  • AES 暗号化には鍵と初期化ベクタが必要。
  • RFC 2898 ではパスワードにソルトを加えて鍵を作る。
  • ソルトと初期化ベクタはランダムに生成する。
    • 同じデータを同じパスワードで暗号化しても、毎回異なるデータが生成される。
    • 複数のファイルを同一のパスワードで暗号化しても、ヒントとなり得るような特徴が生成されにくくなる。
  • 復号にはパスワードとソルトと初期化ベクタが必要。
    • 単一ファイルで完結させるため、ソルトと初期化ベクタはファイルの先頭に置く。
      • 秘匿性はパスワードだけで担保される。
      • ソルトと初期化ベクタはランダムに生成されたため、パスワードの特徴は含まない。
    • 単一ファイルという縛りがなければ、別の場所に置くなど分離することでより解読は困難になる。
  • 暗号化前に圧縮することで冗長性を下げる。
    • 正確に復号できなければ、圧縮を解除することはできない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment