参考:SAORI/1.0(外部サイト)
SAORI/1.0の要求を、DLLのrequest関数ではなくDirect SSTPでベースウェアに送り、ベースウェアに内蔵された機能(内蔵モジュール)をSAORIとして呼び出すための拡張規格です。[SSP 2.9.13]
SHIORIから見ると「DLLを置かずに使えるSAORI」になります。ハッシュ・暗号・乱数・日時の計算など、これまでゴーストごとにDLLを同梱していた機能をベースウェアが提供します。
このページの前半(SAORI/1.0の概要~送信側の手順)はベースウェアに依存しない規格、後半(内蔵モジュール)はSSPが内蔵しているモジュールの説明です。
SAORI/1.0の正しい定義は原典(外部サイト)を参照してください。ここではこのページを読むのに必要な部分だけを簡単にまとめます。
EXECUTE SAORI/1.0 Charset: UTF-8 Sender: 呼び出し元の名前 SecurityLevel: Local Argument0: 1つ目の引数 Argument1: 2つ目の引数
改行はCR+LF、空行で終わります。引数はArgument0から番号順に並べます。値が空の引数も、その位置の引数として数えます。
Charsetは要求の文字コードで、応答も同じ文字コードで返ります。
機能の有無の確認にはGET Version SAORI/1.0を使います。
SAORI/1.0 200 OK Charset: UTF-8 Result: 結果 Value0: 複数の値を返すときの1つ目
Resultが結果。複数の値を返すときはValue0、Value1…にも入る。Direct SSTPと同じくWM_COPYDATA(dwData = 9801)を使い、lpDataにSSTPの要求の代わりにSAORI/1.0の要求を入れて、自分のゴーストの窓(FMOのhwnd)へ送ります。
窓・WM_COPYDATAの扱い(SendMessage系で送る、cbDataなど)はDirect SSTPと同じです。
EXECUTE SAORI/1.0 Module: internal_crypto Charset: UTF-8 Sender: YAYA SecurityLevel: Local HWnd: 1234 Argument0: Digest Argument1: sha256 Argument2: abc
.dllを除いた形で書く(例:internal_crypto、string)。大文字小文字は区別しない。
ErrorDescription: invalid parameter)、知らないモジュールなら400(ErrorDescription: unknown module)。WM_COPYDATAのwParamの窓(Direct SSTPのSSTPと同じ規則)。Localとして扱う(ヘッダの値は見ない)。応答はHWnd(またはwParam)の窓へ、WM_COPYDATA(dwData = 9801)で返ります。内容はSAORI/で始まるSAORI/1.0の応答です。
ベースウェアは要求の処理中に返事を送るので、送信側のSendMessageが戻る前に返事が届きます。
SAORI over DirectSSTPはDirect SSTPだけで受け付けます。
ソケットのSSTPやSSTP over HTTPでSAORI/1.0の要求を送ると、SAORI/1.0 400 Bad RequestとErrorDescription: direct sstp onlyが返ります(SSTP over HTTPでは、HTTPの200の本文としてこの応答が返ります)。
SAORIの要求はSSTPログに記録されません。
ゴーストの窓へ送った場合は、そのゴーストが呼び出し元になります。
ベースウェアの主窓へ送ったときなど、呼び出し元のゴーストが分からない場合は「ゴーストなし」として扱います(Direct SSTPのSSTPのように表示中のゴーストを代わりに当てることはしません)。
ゴーストを使わない処理は普通に動き、ゴーストに依存する処理(結果をイベントで届ける非同期の呼び出し、ゴーストのフォルダを基準にした相対パスなど)は400(ErrorDescription: ghost required)になります。
ReceiverGhostName / ReceiverGhostHWndヘッダは見ません。
ベースウェアは、SAORI/(およびSSTP/)で始まる受信を返事とみなし、要求としては扱いません。
モジュールが使えるかどうかは、GET Version SAORI/1.0にModuleを付けて送って確かめます。使えればSAORI/1.0 200 OK(SSPではResult: SSP Internal)が返ります。
この拡張に対応していないSSP(2.9.12以前)はSAORI/1.0の要求をSSTPの要求として読み、SSTPのエラー(GET VersionにはSSTP/1.0 501 Not Implemented、EXECUTEにはSSTP/1.0 400 Bad Request)を返します。
送信側は「SAORI/1.0 200で始まる返事でなければ使えない」「返事が来なければ使えない」と判定してください。
失敗したときは、応答にErrorDescriptionヘッダで失敗理由(固定の英語の文字列)が付きます。SAORI/1.0の原典には無いヘッダで、SHIORI/3.0の同名のヘッダにならっています。
どのモジュールでも使うものは次のとおりです。ほかは各モジュールの説明を参照してください。
Moduleのモジュールが無い。Argument0)がモジュールに無い。Moduleの欠け・引数の欠けや誤り。互換モジュール(DLLのSAORIの互換)は、原作どおりErrorDescriptionの付かない400を返すことがあります。
SAORIのヘッダの値には、改行と、先頭・末尾の空白を書けません(カンマは書けます)。こうしたデータは、内蔵モジュールでは--data-in=base64などの形式を使って渡してください。
内蔵モジュール(名前がinternal_で始まるもの)の関数は、オプション--async=識別子を付けると非同期で動きます。要求を受け付けると、応答はすぐに204で返り、結果はあとで呼び出し元のゴーストへイベントで届きます。
ゴーストなしの呼び出しでは使えません(400 ghost required)。識別子が空なら400(invalid parameter)です。
時間のかかる処理(大きなファイルのハッシュ、パスワードのハッシュ、鍵の導出など)には--asyncを使ってください。
同時に動く非同期の呼び出しは、SSPではCPUの数ほど(2~4)までです。それを超えた分は、前のものが終わるまで順番を待ちます。
Onで始まれば、成功時はその名前、失敗時は「識別子+Failure」。
Result)、Reference2=モジュール名、Reference3=関数名、Reference4以降=Value0以降。ErrorDescriptionと同じ文字列)、Reference2=モジュール名、Reference3=関数名。引数の数やオプションの名前の誤りは、受付の時点で400になります。引数の中身の誤り(読めない16進など)は、失敗のイベントで届きます。
結果が届く前に呼び出し元のゴーストが終了した場合(SHIORIの再読み込みを含む)、結果は届きません。
SHIORIなどの送信側は、次の手順でSAORI over DirectSSTPを使います。
hwnd)を得る。ModuleとHWnd(返事を受ける窓)を付ける。WM_COPYDATA(dwData = 9801)をSendMessage系の関数で送る。WM_COPYDATA(dwData = 9801)で届いた、SAORI/で始まるデータを応答として読む。使えるかどうかは、最初にGET Versionで確かめてください。
SSPが内蔵している、名前がinternal_で始まるモジュールの共通の決まりです。(同じ名前のSAORI DLLと衝突しないように、internal_で始まる名前にしています。)
これらのモジュールはDirect SSTPからだけ呼べます。DLLとしてloadすることはできません。
Argument0が関数名(大文字小文字は区別しない)、Argument1以降がその関数の引数です。知らない関数名は400(unknown function)です。
引数は、関数ごとに決まった数の位置引数と、その後ろに並べるオプション(--名前=値)からなります。
--で始まっていてもオプションとしては読まず、データとして扱います。--で始まらない値、関数が受け付けないオプションは400(invalid parameter)です。--async=識別子を受け付けます。Argument0: Digest Argument1: sha256 Argument2: SGVsbG8= Argument3: --data-in=base64 Argument4: --out=base64
ハッシュなどはバイト列を扱いますが、SAORIで渡せるのは文字列です。バイト列を受け取る引数にはそれぞれ名前(data、key、password、salt、ciphertext、a、bなど。各関数の説明を参照)があり、--名前-in=形式でその引数の読み方を変えられます。例:--key-in=hex、--data-in=base64。
オプションで渡すバイト列(--aad=、--iv=、--info=)も同じで、--aad-in=hexのように読み方を変えられます。
--charset=の文字コード(既定はUTF-8)のバイト列にする。要求のCharsetではない(送り手の文字コードによらず、Web APIの署名などと同じ値になるように)。その文字コードで表せない文字があれば400(invalid encoding)。invalid encoding)。=)はあってもなくてもよい。それぞれのアルファベット以外の文字は400(invalid encoding)。invalid encoding)。+は空白に戻さない。%の後ろが16進2桁でなければ400(invalid encoding)。internal_cryptoのDigest / HMACのdataだけで使える。
ghost required)。ファイルが無ければ400(file not found)、途中で読めなくなったら500(file read failed)。既定の形式は引数ごとに決まっています(各関数の説明を参照)。デコードした後のデータは1つの引数あたり8MBまで(too large)ですが、Direct SSTPの要求そのものが1MBまでなので、実際にはそれより小さくなります。fileは少しずつ読むので大きさの上限はありません。
バイト列を返す関数は、--out=形式で結果の書き方を変えられます。既定の形式は関数ごとに決まっています。出力の既定の形式は、同じ名前の入力の既定の形式に合わせてあるので、出した値をそのまま別の関数に渡せます。
A-Z a-z 0-9 - . _ ~)以外をすべて%XX(大文字)にする。--charset=の文字コード(既定はUTF-8)の文字列として返す。その文字コードとして正しく戻せないバイト列や、NUL・改行(SAORIのヘッダに書けない)を含むときは500(not text)。--charset=には文字コードの名前(UTF-8、Shift_JIS、EUC-JPなど)を書きます。知らない名前やUTF-16は400(invalid parameter)です。
成功すれば200でResultに結果が入ります。結果が空文字列のときは204です。真偽を返す関数は、200で1(真)または0(偽)を返します。「一致しない」などは失敗ではありません。
失敗時のErrorDescriptionには、共通のもののほかに次のものがあります。
--名前-in=の形式として読めない。圧縮データが壊れている。--data-in=fileのファイルが無い。--out=textで文字列に戻せない。データの形式の変換と、圧縮・展開。
データを別の形式で書き直す(base64⇔16進⇔パーセントエンコード⇔文字列など)。入力は--data-in=、出力は--out=で指定する。
text)--data-in= / --out=(既定はtext) / --charset=Argument0: Convert
Argument1: 48656c6c6f
Argument2: --data-in=hex
Argument3: --out=base64
→ Result: SGVsbG8=
データを圧縮する。
gzip / zlib / deflate(ヘッダの無い生のdeflate)text)--data-in= / --level=(0~9。省略時はzlibの既定) / --out=(既定はbase64) / --charset=圧縮されたデータを展開する。gzipは複数のメンバーをつないだものも読む。
壊れている・途中で切れている・後ろに余計なデータがあるときは400(invalid encoding)。展開した後の大きさも8MBまで(超えると400 too large)。
gzip / zlib / deflate / brotli / zstd(brotliとzstdは展開だけ)base64)--data-in= / --out=(既定はtext) / --charset=Argument0: Decompress
Argument1: gzip
Argument2: H4sIAAAAAAAACvNIzcnJBwCCidH3BQAAAA==
→ Result: Hello
ハッシュ・MAC・共通鍵暗号・鍵の導出・パスワードのハッシュ・データの保護(DPAPI)・ワンタイムパスワード(TOTP)。
Digest / HMACのアルゴリズム名(DeriveKey / TOTPの--digest=も同じ)は次のとおりです。ハイフン・アンダースコアの有無と大文字小文字は区別しません(SHA-256もsha_256もsha256と同じ)。これ以外は400(unsupported algorithm)です。
md5 sha1 sha224 sha256 sha384 sha512 sha512-256 sha3-224 sha3-256 sha3-384 sha3-512 blake2b512 blake2s256
ハッシュ値を求める。
text。fileも使える)--data-in= / --out=(既定はhex) / --charset=Argument0: Digest
Argument1: sha256
Argument2: abc
→ Result: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
文字列は既定でUTF-8のバイト列としてハッシュします。Shift_JISのバイト列のハッシュがほしいときは--charset=Shift_JISを付けてください。
HMACを求める。
text)text。fileも使える)--key-in= / --data-in= / --out=(既定はhex) / --charset=2つのバイト列を、内容によらず同じ時間で比べる。等しければ1、違えば0(長さが違えば0)。
Webhookの署名の照合など、比べる時間から中身を推測されたくない場面に使う。16進の署名どうしを比べるときは--a-in=hex --b-in=hexにすると、大文字小文字の違いを気にしなくてよい。
text)text)--a-in= / --b-in= / --charset=アルゴリズムは次のとおりです(名前のハイフン・アンダースコアの有無と大文字小文字は区別しない)。これ以外は400(unsupported algorithm)です。
鍵(key)の長さは、アルゴリズムの鍵長ちょうど(AES-256とChaCha20は32バイト、AES-128は16バイト)でなければ400(invalid key)です。パスワードから鍵を作るときはDeriveKeyを通してください。
nonce / IVは、暗号化のたびに乱数で作って暗号文の先頭に付けます。--iv=(nonce / IVを指定する)と--pack=none(先頭に付けない)は、ほかのシステムがnonceを別に受け渡す場合のためだけのものです。同じ鍵で同じnonce / IVを2回使うと、GCM・ChaCha20-Poly1305・CTRは暗号として破れます(平文が漏れ、改ざんもできるようになる)。--iv=に固定の値を書いて使い回さないでください。
共通鍵暗号で暗号化する。
text)text)--key-in= / --data-in= / --aad=(追加認証データ。認証付き暗号だけ。--aad-in=、既定の形式はtext) / --iv=(--iv-in=、既定の形式はhex) / --pack=none / --out=(既定はbase64) / --charset=Argument0: Encrypt
Argument1: aes-256-gcm
Argument2: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Argument3: secret message
Argument4: --key-in=hex
→ Result: (毎回違うbase64)
Encryptの暗号文を復号する。
復号できなければ500(authentication failed)。認証付き暗号では、暗号文・認証タグ・--aad=のどれかが違えばこれになる。CBCではパディングが合わないとき。CTRは間違った鍵でも何かしらの値を返す。
text)base64)--key-in= / --ciphertext-in= / --aad= / --aad-in= / --iv= / --iv-in= / --pack=none / --out=(既定はtext) / --charset=--pack=noneのときは--iv=が要ります。--pack=noneでないとき(nonce / IVが暗号文の先頭にあるとき)に--iv=を付けると400(invalid parameter)です。
パスワードなどから、暗号の鍵などに使うバイト列を導出する。方式ごとのパラメータはオプションで指定し、その方式で使わないオプションを書くと400(invalid parameter)。
時間とメモリを使う処理なので、--asyncを推奨。
pbkdf2 / hkdf / scrypt / argon2idtext)text)--password-in= / --salt-in= / 下の方式ごとのオプション / --out=(既定はhex) / --charset=--digest=(既定はsha256) / --iterations=(既定は600000。1~10000000)--digest=(既定はsha256) / --info=(--info-in=、既定の形式はtext)。passwordが入力の鍵(IKM)で、空にはできない。saltは空でもよい。--n=(既定は8192。2の累乗) / --r=(既定は8) / --p=(既定は10。1~16)。N×r×128バイトが12MiBまで。--iterations=(既定は3。1~1000) / --memory=(KiB。既定は12288。lanes×8~12288) / --lanes=(既定は1。1~16)。saltは8バイト以上、長さは4以上。バージョンは1.3(0x13)。秘密鍵(secret)と関連データ(associated data)は使えない。範囲の外は400(out of range)です。既定のパラメータは、OWASPの推奨のうちメモリの上限(12MiB)に収まる組み合わせです。
Argument0: DeriveKey
Argument1: pbkdf2
Argument2: password
Argument3: salt
Argument4: 20
Argument5: --digest=sha1
Argument6: --iterations=4096
→ Result: 4b007901b765489abead49d926f721d065a429c1
パスワードを保存用にハッシュし、PHC文字列(方式・パラメータ・ソルト・ハッシュをまとめた1行の文字列)で返す。ソルトは毎回16バイトの乱数、ハッシュは32バイト、パラメータは下の表で固定(OWASPの推奨のうち、メモリの上限12MiBに収まるもの)。
保存した文字列はVerifyPasswordで照合する。--asyncを推奨。
text)--method=(下の表。既定はargon2id) / --password-in= / --charset=$argon2id$v=19$m=12288,t=3,p=1$ソルト$ハッシュ$scrypt$ln=13,r=8,p=10$ソルト$ハッシュ(ln は N の2を底とする対数)$pbkdf2-sha256$i=600000,l=32$ソルト$ハッシュソルトとハッシュはbase64(パディングなし)です。
パスワードがPHC文字列と一致するかを調べ、一致すれば1、しなければ0を返す。パラメータはPHC文字列から読む。--asyncを推奨。
HashPasswordの出力のほか、同じ形のものなら他の実装が作った文字列も読める($argon2id$(v=の無いもの・v=16も可) / $scrypt$ / $pbkdf2-sha1$ / $pbkdf2-sha256$ / $pbkdf2-sha512$)。ただしメモリの上限(12MiB)を超えるパラメータのもの(argon2-cffiの既定の64MiBなど)は400(out of range)で照合できない。
これ以外の方式(bcryptなど)は400(unsupported algorithm)、形の誤りは400(invalid parameter)、パラメータがDeriveKeyの範囲の外なら400(out of range)。
text)--password-in= / --charset=データを、OSのデータ保護機能(WindowsのDPAPI)で包む。包んだものは、同じPCの同じユーザー(--scope=machineなら同じPCの全ユーザー)でしかUnprotectできない。
セーブデータに置くトークンなどを、ほかのPCやほかのユーザーにコピーされても読めないようにするためのもので、ゴースト同士を分けるものではない(同じユーザーで動くほかのゴーストやプログラムは戻せる)。
text)--data-in= / --scope=user(既定) / --scope=machine / --out=(既定はbase64) / --charset=セーブデータを別のPCへ持っていった場合(SSPのセーブデータ共有、クラウドストレージでの同期、手でのコピーなど)や、Windowsのユーザーを作り直した場合は、Unprotectできなくなります。スクリプトはUnprotectの失敗を「ログインし直してもらう」などとして扱ってください。
Protectで包んだデータを元に戻す。戻せなければ500(authentication failed)。
base64)--data-in= / --out=(既定はtext) / --charset=時刻ベースのワンタイムパスワード(RFC 6238。2段階認証のアプリが出す数字)を求める。桁数に満たなければ先頭を0で埋める。
base32。認証アプリに登録する秘密鍵の文字列)--key-in= / --digits=(既定は6。6~10) / --period=(秒。既定は30。1~86400) / --digest=(既定はsha1。md5は使えない) / --time=(日時。internal_timeの日時の引数と同じ書き方で、ゾーンの無いものはOSのタイムゾーンの時刻。既定は現在時刻) / --charset=Argument0: TOTP
Argument1: GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
Argument2: --time=59
Argument3: --digits=8
→ Result: 94287082
暗号論的に安全な乱数(OpenSSLのRAND_bytes)。
ランダムなバイト列。ソルトやトークンの生成などに。
out of range)--out=(既定はhex)最小以上・最大以下の整数を、偏りなく1つ選ぶ。64ビット符号付き整数の範囲で使える。最小が最大より大きければ400(invalid parameter)。
UUID(RFC 9562)を小文字の正規の表記(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)で返す。
バージョン4は全体がランダム。バージョン7は先頭の48ビットが現在時刻(UNIX時刻のミリ秒)で、作った順に並べやすい(同じミリ秒の中での順序は保証しない)。
--version=4(既定) / --version=7日時の整形と計算。日時は内部ではUTCのエポック秒(1970-01-01T00:00:00Zからの秒数。YAYAのGETSECCOUNTと同じ単位)で扱います。
日時を受け取る引数には、次のどれかを書きます。どれとしても読めなければ400(invalid parameter)です。存在しない日付(2月30日など)も400です。
2026-10-11、2026-10-11T21:00:00+09:00、2026-10-11 21:00、20261011T210000Zなど。小数秒は読み捨てる。
Zや+09:00)の無いものは、--tz=のゾーンの時刻として読む。Sun, 11 Oct 2026 12:00:00 GMTなど。扱える範囲はUTCで0001-01-01~9999-12-31です(--tz=localのときは1601年以降)。範囲の外は400(out of range)です。
ただし、ISO 8601・HTTPの日付の文字列で書けるのは1601年以降です(それより前は400 invalid parameter。整数のエポック秒なら書けます)。
Asia/Tokyo、America/New_Yorkなど)か、Windowsのタイムゾーン名(Tokyo Standard Timeなど)。大文字小文字は区別しない。その日時の夏時間を反映する。
invalid parameter)。UTCとして扱うことはしない。名前付きのゾーンの規則はOSのタイムゾーン情報の範囲のものです。OSの情報に無い昔の規則(多くは2000年代より前)は、OSが持つ最初の年の規則で代用されます。
日時を--tz=のゾーンの日時として整形する。曜日・月名は英語で固定(言語・地域の設定によらない)。
iso8601)--tz=書式は次の名前か、%を使った書式です。知らない書式名・知らない%の文字は400(invalid parameter)です。
2026-10-11T21:00:00+09:00。オフセットが0なら末尾はZ。Sun, 11 Oct 2026 12:00:00 GMT。--tz=によらず常にGMT。%Y(西暦4桁) %y(西暦の下2桁) %m(月) %d(日) %H(時) %M(分) %S(秒) %j(年の何日目) %u(曜日 月=1~日=7) %w(曜日 日=0~土=6) %a(Sun) %A(Sunday) %b(Jan) %B(January) %z(+0900) %:z(+09:00) %s(エポック秒) %%(%)。
%以外の文字はそのまま出力する。Argument0: Format
Argument1: 0
Argument2: %Y/%m/%d %H:%M (%a)
Argument3: --tz=+09:00
→ Result: 1970/01/01 09:00 (Thu)
日時に量を足した日時を、エポック秒で返す。量は負でもよい。
second / minute / hourは経過時間で足す。day以上は--tz=のゾーンの暦で足す(夏時間の切り替えをまたいでも同じ時刻になる)。
月末は丸める(1月31日+1か月=2月28日または29日)。
second minute hour day week month year(複数形のdaysなども可)--tz=日時2-日時1を、単位で数えた整数で返す。端数は0の方向へ切り捨てる。
day以上は--tz=のゾーンの暦で数える。たとえば日時2が日付だけ(その日の0時)なら、「あと何日」は--tz=のゾーンで日付が変わる時刻に1つ減る。month / yearは暦の月で数え、Addで足して越えない最大の数を返す(1月31日からは、平年なら2月28日、うるう年なら2月29日の同じ時刻で1か月)。
--tz=Argument0: Diff
Argument1: 2026-12-20T10:00
Argument2: 2026-12-24
Argument3: day
→ Result: 3(3日と14時間なので3)
カレンダーの日付どうしの差(「今日から何日後か」)がほしいときは、日時1も日付だけにしてください(Formatの%Y-%m-%dで今日の日付を作れます)。
文字列の処理。
文字列をUnicodeの正規化形式にそろえる。半角カナと全角カナ、合成済みの文字と結合文字の並びなど、見た目が同じで符号が違う文字列を比べる前にそろえるのに使う。
OSの機能(Windows Vista以降のNormalizeString)を使う。使えないOSでは500(internal error)。対になっていないサロゲートを含む文字列は400(invalid encoding)。
NFC / NFD / NFKC / NFKD(大文字小文字は区別しない。これ以外は400 unsupported algorithm)Argument0: Normalize
Argument1: NFKC
Argument2: ガイド①
→ Result: ガイド1
SSPは、よく使われていた次のSAORI DLLの互換の機能を内蔵しています。ゴーストがこれらのDLLをloadすると、SSPはDLLを読まずに内蔵の機能に差し替えます(ファイル名で判定します)。
[SSP 2.9.13] これらはDirect SSTPからも、DLL名から.dllを除いた名前(Module: stringなど)で呼べます。引数と戻り値は原作のSAORIのままです。
houchi clipcopy textcopy saori_ip saori_cpuid mciaudio cpuusage httpc string hanayu
load / unloadが無いので、ゴーストごとに状態を持つもの(mciaudio / httpc / string / hanayu)は、そのゴーストの最初の呼び出しで用意し、ゴーストの終了(SHIORIの再読み込みを含む)で捨てます。DLLとしてloadしたものとは状態を共有しません。httpcの取得・保存とbg、hanayuのshow、mciaudioのload / play / loop / stop)は400(ghost required)になります。houchiはloadしたときに働くものなので、Direct SSTPから呼んでも何も起きません。httpcの取得はbgを付けて行ってください(付けないと、取得の間SSPの画面が止まります)。