Skip to content

Commit fb8b273

Browse files
authored
Merge pull request #38 from gordonkeener/main
Random password generator
2 parents fa89f54 + 8cbb696 commit fb8b273

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/************************************************************************************************
2+
Random Password Generation
3+
1. Defining keyword arguments control the length and characters
4+
used in the password.
5+
2. Building a macro variable containing all allowed characters.
6+
3. In a loop, using %SUBSTR(%SYSFUNC(RAND)) to pluck random characters...
7+
4. ...and appending them to the growing password.
8+
5. Testing our macro via a simple loop and various settings.
9+
6. Using a random password for an encryption key on a tempoarary dataset.
10+
11+
Keywords: Macro
12+
SAS Versions: SAS 9, SAS Viya
13+
Documentation: https://go.documentation.sas.com/doc/en/pgmsascdc/default/mcrolref/titlepage.htm
14+
15+
NOTES:
16+
1. Only tested with SAS OnDemand for Academics, as that is the only
17+
SAS version I have access to.
18+
2. Does not (currently) support unmatched single or double quote characters
19+
in the special characterw string. I have not tried mismatched left or
20+
right parentheses, either.
21+
************************************************************************************************/
22+
23+
/************************************************************************************************
24+
1. Defining keyword arguments control the length and characters
25+
used in the password.
26+
a. len= is the desired length of the password, defaulting to 24
27+
b. digits= is a Boolean flag, defaulting to TRUE. When TRUE (non-zero),
28+
digits 0-9 are available for use in the password.
29+
c. special= is a set of special characters to also use in passwords. Defaults
30+
to those shown in the %NRSTR(). Use special= to not have any specials.
31+
************************************************************************************************/
32+
%MACRO RANDPASS(len=24,digits=1,special=%NRSTR(@%&#!?.-_+*,/;:));
33+
%LOCAL pass;
34+
%LOCAL chars;
35+
%LOCAL numchars;
36+
%LOCAL pick;
37+
%LOCAL i;
38+
39+
/************************************************************************************************
40+
2. Building a macro variable containing all allowed characters.
41+
a. Add the lowercase letters.
42+
b. Add uppercase characters via %UPCASE().
43+
c. Add digits, if requested.
44+
d. Add special characters. Note the use of %SUPERQ() to avoid issues
45+
with &s and %s that may be present in the string.
46+
************************************************************************************************/
47+
%LET chars=abcdefghijklmnopqrstuvwxyz;
48+
%LET chars=&chars%UPCASE(&chars);
49+
%IF &digits %THEN
50+
%LET chars=&chars.0123456789;
51+
%LET chars=&chars%SUPERQ(special);
52+
53+
%LET numchars=%length(&chars);
54+
55+
%DO i=1 %TO &len;
56+
/************************************************************************************************
57+
3. In a loop, using %SUBSTR(%SYSFUNC(RAND)) to pluck random characters...
58+
a. RAND('INTEGER', n) produces a random intger between 1 and n, inclusive
59+
which is exactly what we need.
60+
b. %SYSFUNC() invokes it from Macro.
61+
c. %SUBSTR(string, N, 1) plucks the Nth character from the string.
62+
************************************************************************************************/
63+
%LET pick=%SUBSTR(&chars,%SYSFUNC(RAND(INTEGER,&numchars)),1);
64+
65+
/************************************************************************************************
66+
4. ...and appending them to the growing password.
67+
a. Note in particular the shenanigans here.
68+
b. Simply coding "%LET pass=%SUPERQ(pass)&pick" _does_not_work_. Hidden
69+
macro quoting characters end up in &pass, in a nested fashion, and
70+
this produces nested Macro function WARNINGs when the password length
71+
grows to about 12 characters, which is unacceptable.
72+
c. The %QSUBSTR() manages to avoid this problem, and also avoids attempts
73+
to resolve any &foo that might appear in the growing password.
74+
d. However, %QSUBSTR() can't work on an empty string, so we have to avoid
75+
it first time though, hence the %IF.
76+
************************************************************************************************/
77+
%IF &i = 1 %THEN
78+
%LET pass=&pick;
79+
%ELSE
80+
%LET pass=%QSUBSTR(%SUPERQ(pass),1)&pick;
81+
%END;
82+
83+
%* The result of the macro is this generated password;
84+
%SUPERQ(pass)
85+
86+
%MEND;
87+
88+
/************************************************************************************************
89+
5. Testing our macro via a simple loop, and various settings.
90+
a. We need to define a macro for this. Macro now allows %IF statements in
91+
"open code", but not loops.
92+
b. Just two cases here. All-defaults, plus longer-no-digits-no-specials.
93+
c. The "test" just prints them to the log, for eyeballing.
94+
d. Then we run the loop 3 times to get an idea as to what it's doing.
95+
************************************************************************************************/
96+
%MACRO TEST(n);
97+
%DO i=1 %TO &n;
98+
%PUT pw1=%RANDPASS();
99+
%PUT pw2=%RANDPASS(len=30,digits=0,special=);
100+
%END;
101+
%MEND;
102+
103+
%TEST(3);
104+
105+
106+
107+
/************************************************************************************************
108+
6. Using a random password for an encryption key on a tempoarary dataset.
109+
a. Generate a longish (60 characters) password, safe from prying eyes.
110+
b. We import a list of NSA recruits from a secret location no one has
111+
ever heard of, and protect it with AES encryption with our generated
112+
password.
113+
c. Now we can run analytics on it safely. Here, we run PROC MEANS, and
114+
then ponder why the recruits are all teenagers.
115+
d. Now we can drop the table (I used PROC SQL), knowing that even if the
116+
data was recovered from the disk, the encryption renders it unreadable.
117+
************************************************************************************************/
118+
%LET pw=%randpass(len=60);
119+
%*PUT pw=&pw <== to see it, but its supposed to be a secret;
120+
121+
DATA nsaclass(ENCRYPT=aes ENCRYPTKEY="&pw");
122+
SET sashelp.class;
123+
RUN;
124+
125+
PROC MEANS DATA=nsaclass(ENCRYPTKEY="&pw");
126+
RUN;
127+
128+
PROC SQL;
129+
DROP TABLE nsaclass;
130+
QUIT;

0 commit comments

Comments
 (0)