Inconsistent behavior of undocumented Encode->Import->StringToStream->Get on password-locked stream

Let's save a definition. Encode it with password/key and Get it again.

We will not use Get directly on directory but with combination of Get+StringToStream+Import.

Get works with streams since V9.0 so I see no reason not to go this way.

file = FileNameJoin[{$TemporaryDirectory, "def.m"}]; fileEnc = FileNameJoin[{$TemporaryDirectory, "def.enc"}];
DeleteFile /@ {file, fileEnc} //Quiet; (*just in case*)

ClearAll @ f;
f[x_] := x^2;
Save[file, f];

ClearAll @ f;
stream = StringToStream @ Import[fileEnc, "Text"];
Close[stream];
f[2]


4

Yeah, great... fortunately I've tested this with different password:

password = "kuba";

ClearAll @ f;
stream = StringToStream @ Import[fileEnc, "Text"];
Close[stream];
f[2]

Syntax::sntx: Invalid syntax in or before "f[x_] :eeev" (line 1 of "String["(*!1N!*)4mx.
^    w24yf0¡'h;1;U.#+"]")
f[2]


From my observations it seems to be quite random.

p.s. using Get directly will work. but this is not what I'm after.

Reproduced on V9 V10 Win7

• Hmm - odd, but as Get's documentation says nothing about Get[stream,password], this comprises the use of an undocumented feature and therefore may or may not do what one thinks it might. – Jinxed Feb 28 '15 at 14:27
• @Jinxed I agree. On the other hand documentation is not always accurate so I assumed it should work. – Kuba Feb 28 '15 at 14:41
• On the one hand, the feature you use is undocumented, on the other, it is behaving oddly nonetheless. Maybe you could add a hint, that this actually is about an undocumented feature? – Jinxed Feb 28 '15 at 22:45
• It would be interesting to analyze, exactly which conditions make a "good" or "bad" password in your specific context. Maybe I can take a look later on. – Jinxed Mar 1 '15 at 0:23
• @Jinxed Thanks for the edit. – Kuba Mar 1 '15 at 8:34

Being able to use this provides us with a way to produce quite secure CDFs designed for FreePlayer.

Maybe it is a problem with encoding somewhere or with overloaded definitions of Get.

Nevermind, as pointed out by Rolf Mertig if one uses OpenRead with DefineInputStreamMethod to convert a binary file to a stream, everything seems to work.

Steps:

• DefineInputStreamMethod["ByteList", ...
• binarydata = Import[encodedFile, "Binary"]
• stream = OpenRead["whateverName", Method -> {"ByteList", "Bytes" -> binarydata}]
• Get[stream, password]

Execution:

(* This part is taken bit by bit from the documentation
of DefineInputStreamMethod *)

DefineInputStreamMethod["ByteList", {
"ConstructorFunction" ->
Function[{name, caller, opts},
If[MatchQ[opts, {___, "Bytes" -> {_Integer ...}, ___}],
{True, {0, "Bytes" /. opts}},
{False, $Failed} ]], "ReadFunction" -> Function[{state, n}, Module[{pos = state[[1]], bytes = state[[2]], bytesRead}, If[pos >= Length[bytes], {{}, state}, bytesRead = Part[bytes, pos + 1 ;; Min[Length[bytes], pos + n]]; {bytesRead, {pos + Length[bytesRead], bytes}} ] ]], "EndOfFileQFunction" -> ({#[[1]] >= Length[#[[2]]], #} &) }]  file = FileNameJoin[{$TemporaryDirectory, "def.m"}];
fileEnc = FileNameJoin[{\$TemporaryDirectory, "def.enc"}];
DeleteFile /@ {file, fileEnc} // Quiet;(*just in case*)
ClearAll@f;

f[x_] := x^2;
Save[file, f];

ClearAll@f;

bin = Import[fileEnc, "Binary"];

stream = OpenRead["dumpsave", Method -> {"ByteList", "Bytes" -> bin}];

Close[stream];

f[2]

4


No "real" answer, but a deeper analysis into the behavior you experienced:

file = "original.m";
fileEnc = "enc.m";

(* alphabet for password: [0-9a-zA-Z] *)
alphabet=Sort[CharacterRange @@@ {{"a", "z"}, {"A", "Z"}, {"0", "9"}}//Flatten,
ToCharacterCode@#1 < ToCharacterCode@#2&];

(* checking and recording success of decryption together with password *)
res=Join@@Quiet@Table[Block[{keys=Tuples[alphabet,len],stream},
ClearAll@f;
(Encode[file,fileEnc,StringJoin@#];
{StringJoin@#,ToCharacterCode/@#,
stream =
StringToStream@Import[fileEnc,{"Text","Plaintext"}];
Check[Get[stream, StringJoin@#];Quiet@Close@stream;True,
Quiet@Close@stream; False]})&/@keys],{len,2}];


I did this for passwords of length 1 and 2 only, with disturbing results:

(* useable passwords of length==1 (black) *)
ArrayPlot@Boole@{Cases[res,{_,b_,c_}:>{c}/;Length@b==1]}


(* usable passwords of length==2 (black), 62x62 array *)
ArrayPlot[Partition[Boole@Cases[res,{_, b_, c_}:>{c}/;Length@b==2],62],ImageSize->Large]


Even after checking the character codes (and did multiple operations on them), I fail to find a pattern in what passwords are valid in this context, and which are not. :(

Conclusion

Using Get on encoded streams is undocumented and obviously not meant to be called by end users, as usable passwords seem to have to be picked by trial-and-error.

• Or I can just give the user a message: "please choose different password, this on is not safe" :D – Kuba Mar 2 '15 at 7:48