12

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];

password = "key";    
Encode[file, fileEnc, password];

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

4

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

password = "kuba";

Encode[file, fileEnc, password];

ClearAll @ f;
stream = StringToStream @ Import[fileEnc, "Text"];
Get[stream , password];
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

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • 2
    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
  • 1
    @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 01 '15 at 00:23
  • @Jinxed Thanks for the edit. – Kuba Mar 01 '15 at 08:34
  • @Jinxed: I see this is an old thread but I need to disagree, the Get["source","key"] syntax is clearly documented in the Encode page. While this is admittedly hard to find, it is part of the official documentation: – Albert Retey Dec 05 '16 at 21:46
  • @AlbertRetey: But surely not, where it belongs: In Get[]'s part of the docs. – Jinxed Dec 06 '16 at 21:58
  • @Jinxed: That's what I said: it is hard to find - and I'm not trying to defend that - but that doesn't make it undocumented, does it? Whether it is hard to find or not, it is obviously part of the official documentation, so I think one surely can expect it to work as documented... – Albert Retey Dec 06 '16 at 22:22
  • @Jinxed: asking the other way around: would you accept an answer from support that "this feature can not be expected to work, that's why we did hide the documentation for it on a page where noone finds it" ?!? – Albert Retey Dec 06 '16 at 22:26
  • @AlbertRetey: Is this supposed to be a practical joke? – Jinxed Dec 07 '16 at 22:26

2 Answers2

8

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];

password = "kuba";
Encode[file, fileEnc, password];

ClearAll@f;

bin = Import[fileEnc, "Binary"];

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

Get[stream, password];

Close[stream];

f[2]
4
Kuba
  • 136,707
  • 13
  • 279
  • 740
  • This "ByteList" definition is in the documentation : https://reference.wolfram.com/language/ref/DefineInputStreamMethod.html – Chris Degnen Jul 24 '20 at 14:44
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==1

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

usable passwords of length==2

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.

Jinxed
  • 3,753
  • 10
  • 24
  • 1
    Or I can just give the user a message: "please choose different password, this on is not safe" :D – Kuba Mar 02 '15 at 07:48