{"id":156,"date":"2012-02-06T00:21:45","date_gmt":"2012-02-06T00:21:45","guid":{"rendered":"http:\/\/joelinoff.com\/blog\/?p=156"},"modified":"2012-02-27T00:59:44","modified_gmt":"2012-02-27T00:59:44","slug":"nx-password-scrambling-and-unscrambling-algorithms-python","status":"publish","type":"post","link":"https:\/\/joelinoff.com\/blog\/?p=156","title":{"rendered":"NX password scrambling and unscrambling algorithms in python 2.7"},"content":{"rendered":"<p>The NX web page entitled &#8220;<a title=\"http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\" href=\"http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\"><span><strong>The password scrambling algorithm in NX client<\/strong><\/span><\/a>&#8221; describes their password scrambling algorithm in C++ and perl but it does not describe it in python. Nor does it describe how to unscramble the data.<\/p>\n<p>This blog describes how I implemented both algorithms in python. I was surprised that NX didn&#8217;t use a standard symmetrical key algorithm like blowfish or DES for storing the keys but they must have some good reason.<br \/>\n<!--more--><\/p>\n<h1>DOWNLOADS<\/h1>\n<table>\n<thead>\n<tr>\n<th>Script<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.py\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.py\" target=\"_blank\">nxpasswd.py<\/a><\/td>\n<td>Scrambling algorithm.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.cc\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.cc\" target=\"_blank\">nxpasswd.cc<\/a><\/td>\n<td>Scrambling algorithm in vanilla C++. The NoMachine example assumes that you are using the Qt library.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxdecode.py\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxdecode.py\" target=\"_blank\">nxdecode.py<\/a><\/td>\n<td>Unscrambling algorithm.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"http:\/\/projects.joelinoff.com\/nx\/test.sh\" title=\"http:\/\/projects.joelinoff.com\/nx\/test.sh\" target=\"_blank\">test.sh<\/a><\/td>\n<td>Test script.<\/td>\n<\/tr>\n<\/tbody\n<\/table>\n<h1>SCRAMBLING ALGORITHM<\/h1>\n<p>The scrambling algorithm is more for visual obfuscation than security as is clearly stated on their web page. <\/p>\n<p>[crayon lang=&#8221;python&#8221; toolbar=&#8221;always&#8221; title=&#8221;SCRAMBLE&#8221;]<br \/>\n#!\/usr\/bin\/env python<br \/>\n# ================================================================<br \/>\n# Author : Joe Linoff<br \/>\n# Date : 2012-02-04<br \/>\n# Version: 1.0<br \/>\n#<br \/>\n# Implements the password scrambling algorithm used by NX in python as<br \/>\n# defined here:<br \/>\n#<br \/>\n# http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125<br \/>\n# https:\/\/gist.github.com\/902387<br \/>\n#<br \/>\n# Usage:<br \/>\n#     .\/nxpasswd.py import os<br \/>\n# ================================================================<br \/>\nimport os<br \/>\nimport sys<br \/>\nimport string<br \/>\nimport time<\/p>\n<p>validCharList = [<br \/>\n         &#8220;!&#8221;, &#8220;#&#8221;, &#8220;$&#8221;, &#8220;%&#8221;, &#8220;&#038;&#8221;, &#8220;(&#8220;, &#8220;)&#8221;, &#8220;*&#8221;, &#8220;+&#8221;, &#8220;-&#8220;,<br \/>\n         &#8220;.&#8221;, &#8220;0&#8221;, &#8220;1&#8221;, &#8220;2&#8221;, &#8220;3&#8221;, &#8220;4&#8221;, &#8220;5&#8221;, &#8220;6&#8221;, &#8220;7&#8221;, &#8220;8&#8221;,<br \/>\n         &#8220;9&#8221;, &#8220;:&#8221;, &#8220;;&#8221;, &#8220;<\", \">&#8220;, &#8220;?&#8221;, &#8220;@&#8221;, &#8220;A&#8221;, &#8220;B&#8221;, &#8220;C&#8221;,<br \/>\n         &#8220;D&#8221;, &#8220;E&#8221;, &#8220;F&#8221;, &#8220;G&#8221;, &#8220;H&#8221;, &#8220;I&#8221;, &#8220;J&#8221;, &#8220;K&#8221;, &#8220;L&#8221;, &#8220;M&#8221;,<br \/>\n         &#8220;N&#8221;, &#8220;O&#8221;, &#8220;P&#8221;, &#8220;Q&#8221;, &#8220;R&#8221;, &#8220;S&#8221;, &#8220;T&#8221;, &#8220;U&#8221;, &#8220;V&#8221;, &#8220;W&#8221;,<br \/>\n         &#8220;X&#8221;, &#8220;Y&#8221;, &#8220;Z&#8221;, &#8220;[&#8220;, &#8220;]&#8221;, &#8220;_&#8221;, &#8220;a&#8221;, &#8220;b&#8221;, &#8220;c&#8221;, &#8220;d&#8221;,<br \/>\n         &#8220;e&#8221;, &#8220;f&#8221;, &#8220;g&#8221;, &#8220;h&#8221;, &#8220;i&#8221;, &#8220;j&#8221;, &#8220;k&#8221;, &#8220;l&#8221;, &#8220;m&#8221;, &#8220;n&#8221;,<br \/>\n         &#8220;o&#8221;, &#8220;p&#8221;, &#8220;q&#8221;, &#8220;r&#8221;, &#8220;s&#8221;, &#8220;t&#8221;, &#8220;u&#8221;, &#8220;v&#8221;, &#8220;w&#8221;, &#8220;x&#8221;,<br \/>\n         &#8220;y&#8221;, &#8220;z&#8221;, &#8220;{&#8220;, &#8220;|&#8221;, &#8220;}&#8221;<br \/>\n         ]<\/p>\n<p>def encodePassword(p):<br \/>\n    sPass = &#8220;:&#8221;<br \/>\n    sTmp = &#8220;&#8221;<\/p>\n<p>    if p in [&#8221;, None]:<br \/>\n        return &#8221;<\/p>\n<p>    for i in range(len(p)):<br \/>\n        sPass += &#8216;%d:&#8217; % (ord(p[i])+i+1)<\/p>\n<p>    return sPass<\/p>\n<p>def findCharInList(c):<br \/>\n    global validCharList<br \/>\n    for i in range(len(validCharList)):<br \/>\n        if validCharList[i] == c:<br \/>\n            return i<br \/>\n    return -1<\/p>\n<p>def getRandomValidCharFromList():<br \/>\n    global validCharList<br \/>\n    lt = time.localtime()<br \/>\n    s = lt.tm_sec<br \/>\n    if os.getenv(&#8216;NXPASSWD_NONRANDOM&#8217;):<br \/>\n        s = int(os.getenv(&#8216;NXPASSWD_NONRANDOM&#8217;))<br \/>\n    return validCharList[s]<\/p>\n<p>def URLEncode(url):<br \/>\n    url = url.replace(&#8216;&#038;&#8217;,&#8217;&amp;&#8217;)<br \/>\n    url = url.replace(&#8216;&#8221;&#8216;,&#8217;&quot;&#8217;)<br \/>\n    url = url.replace(&#8220;&#8216;&#8221;,&#8217;&apos;&#8217;)<br \/>\n    url = url.replace(&#8216;<','&lt;')\n    url = url.replace('>&#8216;,&#8217;&gt;&#8217;)<br \/>\n    return url<\/p>\n<p>def scrambleString(s):<br \/>\n    global validCharList<br \/>\n    dummyString = &#8220;{{{{&#8221;<br \/>\n    sRet = &#8221;<\/p>\n<p>    if s in [&#8221;, None]:<br \/>\n        return &#8221;<\/p>\n<p>    s1 = encodePassword(s)<\/p>\n<p>    if len(s1) < 32:\n        sRet += dummyString\n        \n    sRet += s1[::-1]  # reverse string\n    \n    if len(sRet) < 32:\n        sRet += dummyString\n\n    ch = getRandomValidCharFromList()\n    k = ord(ch) + len(sRet) - 2\n    sRet = ch + sRet\n    \n    for i in range(1,len(sRet)):\n        j = findCharInList(sRet[i])\n        if j == -1:\n            return sRet\n        n = (j + k * (i+1)) % len(validCharList)\n        sRet = sRet[:i] + validCharList[n] + sRet[i+1:]\n\n    sRet += chr((ord(getRandomValidCharFromList())) + 2)\n    sRet = URLEncode(sRet)\n    return sRet\n\nfor i in range(1,len(sys.argv)):\n    p=sys.argv[i]\n    print scrambleString(p)\n[\/crayon]\n\n\n<h1>\u00a0UNSCRAMBLING ALGORITHM<\/h1>\n<p>This algorithm shows how to unscramble the output from the previous script to regenerate the original password.<br \/>\n[crayon lang=&#8221;python&#8221; toolbar=&#8221;always&#8221; title=&#8221;UNSCRAMBLE&#8221;]<br \/>\n#!\/usr\/bin\/env python<br \/>\n# ================================================================<br \/>\n# Author : Joe Linoff<br \/>\n# Date : 2012-02-04<br \/>\n# Version: 1.0<br \/>\n#<br \/>\n# Unscrambles passwords creating by the NX scrambling algorithm.<br \/>\n#<br \/>\n# http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125<br \/>\n#<br \/>\n# Usage:<br \/>\n#    % .\/nxpasswd.py <password>\n#    <scramble><br \/>\n#    % .\/nxdecode.py <scramble><br \/>\n#    <password>\n# ================================================================<br \/>\nimport sys<br \/>\nimport re<\/p>\n<p># We know that the first character is always a &#8216;:&#8217;<br \/>\n# We can use that to figure out the correct k value.<br \/>\nvalidCharList = [<br \/>\n         &#8220;!&#8221;, &#8220;#&#8221;, &#8220;$&#8221;, &#8220;%&#8221;, &#8220;&#038;&#8221;, &#8220;(&#8220;, &#8220;)&#8221;, &#8220;*&#8221;, &#8220;+&#8221;, &#8220;-&#8220;,<br \/>\n         &#8220;.&#8221;, &#8220;0&#8221;, &#8220;1&#8221;, &#8220;2&#8221;, &#8220;3&#8221;, &#8220;4&#8221;, &#8220;5&#8221;, &#8220;6&#8221;, &#8220;7&#8221;, &#8220;8&#8221;,<br \/>\n         &#8220;9&#8221;, &#8220;:&#8221;, &#8220;;&#8221;, &#8220;<\", \">&#8220;, &#8220;?&#8221;, &#8220;@&#8221;, &#8220;A&#8221;, &#8220;B&#8221;, &#8220;C&#8221;,<br \/>\n         &#8220;D&#8221;, &#8220;E&#8221;, &#8220;F&#8221;, &#8220;G&#8221;, &#8220;H&#8221;, &#8220;I&#8221;, &#8220;J&#8221;, &#8220;K&#8221;, &#8220;L&#8221;, &#8220;M&#8221;,<br \/>\n         &#8220;N&#8221;, &#8220;O&#8221;, &#8220;P&#8221;, &#8220;Q&#8221;, &#8220;R&#8221;, &#8220;S&#8221;, &#8220;T&#8221;, &#8220;U&#8221;, &#8220;V&#8221;, &#8220;W&#8221;,<br \/>\n         &#8220;X&#8221;, &#8220;Y&#8221;, &#8220;Z&#8221;, &#8220;[&#8220;, &#8220;]&#8221;, &#8220;_&#8221;, &#8220;a&#8221;, &#8220;b&#8221;, &#8220;c&#8221;, &#8220;d&#8221;,<br \/>\n         &#8220;e&#8221;, &#8220;f&#8221;, &#8220;g&#8221;, &#8220;h&#8221;, &#8220;i&#8221;, &#8220;j&#8221;, &#8220;k&#8221;, &#8220;l&#8221;, &#8220;m&#8221;, &#8220;n&#8221;,<br \/>\n         &#8220;o&#8221;, &#8220;p&#8221;, &#8220;q&#8221;, &#8220;r&#8221;, &#8220;s&#8221;, &#8220;t&#8221;, &#8220;u&#8221;, &#8220;v&#8221;, &#8220;w&#8221;, &#8220;x&#8221;,<br \/>\n         &#8220;y&#8221;, &#8220;z&#8221;, &#8220;{&#8220;, &#8220;|&#8221;, &#8220;}&#8221;<br \/>\n         ]<\/p>\n<p>def findCharInList(c):<br \/>\n    global validCharList<br \/>\n    for i in range(len(validCharList)):<br \/>\n        if validCharList[i] == c:<br \/>\n            return i<br \/>\n    return -1<\/p>\n<p>def findK(ch0,i=5,chf=&#8217;:&#8217;):<br \/>\n    global validCharList<br \/>\n    j = findCharInList(chf)<br \/>\n    for k in range(0,200):<br \/>\n        n = (j + k * (i+1)) % len(validCharList)<br \/>\n        m = validCharList[n]<br \/>\n        if m == ch0:<br \/>\n            return k<br \/>\n    return -1<\/p>\n<p>def unScramble(scramble):<br \/>\n    scramble=URLDecode(scramble)<\/p>\n<p>    # Were dummy strings appended\/prepended?<br \/>\n    # Need 3 tests to check.<br \/>\n    tests = []<br \/>\n    scramble=scramble[1:]<br \/>\n    scramble=scramble[:-1]<br \/>\n    tests.append([scramble,0])<\/p>\n<p>    scramble=scramble[4:]<br \/>\n    tests.append([scramble,4])<\/p>\n<p>    scramble=scramble[:-4]<br \/>\n    tests.append([scramble,4])<\/p>\n<p>    for data in tests:<br \/>\n        scramble=data[0]<br \/>\n        i=data[1]<br \/>\n        k=findK(scramble[0],i+1)<\/p>\n<p>        charset=&#8221;<br \/>\n        for ch in scramble:<br \/>\n            i += 1<br \/>\n            n = findCharInList(ch)<br \/>\n            for j in range(len(validCharList)):<br \/>\n                zn = (j + k * (i+1)) % len(validCharList)<br \/>\n                if n == zn:<br \/>\n                    ich = validCharList[j]<br \/>\n                    charset += ich<br \/>\n                    break<\/p>\n<p>        charset = charset[::-1]<br \/>\n        ordvals = charset.split(&#8216;:&#8217;)<br \/>\n        passwd = &#8221;<br \/>\n        i=0<br \/>\n        ok = True<br \/>\n        for ordval in ordvals:<br \/>\n            if len(ordval)>0:<br \/>\n                m = re.search(&#8216;^[0-9]+$&#8217;,ordval)<br \/>\n                if m:<br \/>\n                    j = int(ordval)-i-1<br \/>\n                    passwd += chr(j)<br \/>\n                    i += 1<br \/>\n                else:<br \/>\n                    ok = False<br \/>\n                    break<\/p>\n<p>        if ok:<br \/>\n            return passwd<\/p>\n<p>    return None<\/p>\n<p>def URLDecode(url):<br \/>\n    url = url.replace(&#8216;&amp;&#8217;,&#8217;&#038;&#8217;)<br \/>\n    url = url.replace(&#8216;&quot;&#8217;,'&#8221;&#8216;)<br \/>\n    url = url.replace(&#8216;&apos;&#8217;,&#8221;&#8216;&#8221;)<br \/>\n    url = url.replace(&#8216;&lt;&#8216;,&#8217;<')\n    url = url.replace('&gt;','>&#8216;)<br \/>\n    return url<\/p>\n<p>for i in range(1,len(sys.argv)):<br \/>\n    p=sys.argv[i]<br \/>\n    print unScramble(p)<br \/>\n[\/crayon]<\/p>\n<h1>EXAMPLE RUNS<\/h1>\n<p>Below I have run both programs to show they work using this test script.<\/p>\n<p>[crayon lang=&#8221;bash&#8221; toolbar=&#8221;always&#8221; title=&#8221;TEST SCRIPT&#8221;]<br \/>\n#!\/bin\/bash<br \/>\nps=(<br \/>\n    &#8216;a&#8217;<br \/>\n    &#8216;1&#8217;<br \/>\n    &#8217;12&#8217;<br \/>\n    &#8216;123&#8217;<br \/>\n    &#8216;1234&#8217;<br \/>\n    &#8216;12345&#8217;<br \/>\n    &#8216;123456&#8217;<br \/>\n    &#8216;1234567&#8217;<br \/>\n    &#8216;12345678&#8217;<br \/>\n    &#8216;123456789&#8217;<br \/>\n    &#8216;1234567890&#8217;<br \/>\n    &#8216;1234567890a&#8217;<br \/>\n    &#8216;11111111111&#8217;<br \/>\n    &#8216;1234567890ab&#8217; )<br \/>\npassed=0<br \/>\nfailed=0<br \/>\ntotal=0<br \/>\nfor p in ${ps[@]} ; do<br \/>\n    (( total++ ))<br \/>\n    echo<br \/>\n    echo &#8220;# ================================================&#8221;<br \/>\n    echo &#8220;# test       : $total&#8221;<br \/>\n    echo &#8220;# password   : $p&#8221;<br \/>\n    s=$(eval .\/nxpasswd.py &#8220;&#8216;$p'&#8221; | tail -1)<br \/>\n    echo &#8220;# scrambled  : $s&#8221;<br \/>\n    px=$(eval .\/nxdecode.py &#8220;&#8216;$s'&#8221; | tail -1)<br \/>\n    echo &#8220;# unscrambled: $px&#8221;<br \/>\n    if [[ &#8220;$px&#8221; != &#8220;$p&#8221; ]] ; then<br \/>\n        echo &#8220;# status     : FAILED&#8221;<br \/>\n        (( failed++ ))<br \/>\n    else<br \/>\n        echo &#8220;# status     : OK&#8221;<br \/>\n        (( passed++ ))<br \/>\n    fi<br \/>\ndone<br \/>\necho<br \/>\necho &#8220;# SUMMARY&#8221;<br \/>\necho &#8220;#   Passed : $passed&#8221;<br \/>\necho &#8220;#   Failed : $failed&#8221;<br \/>\necho &#8220;#   Total  : $total&#8221;<br \/>\n[\/crayon]<\/p>\n<p>[crayon lang=&#8221;bash&#8221; toolbar=&#8221;always&#8221; title=&#8221;EXAMPLE RUNS&#8221;]<br \/>\n# ================================================<br \/>\n# test       : 1<br \/>\n# password   : a<br \/>\n# scrambled  : _Kbv1as.EAUl$a<br \/>\n# unscrambled: a<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 2<br \/>\n# password   : 1<br \/>\n# scrambled  : _Kbv1ak)EAUl$a<br \/>\n# unscrambled: 1<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 3<br \/>\n# password   : 12<br \/>\n# scrambled  : aUq1K#7Tu)Jkl*Fbc<br \/>\n# unscrambled: 12<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 4<br \/>\n# password   : 123<br \/>\n# scrambled  : b_}Bb?VwAVyCV{EJj-Ld<br \/>\n# unscrambled: 123<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 5<br \/>\n# password   : 1234<br \/>\n# scrambled  : bf+NqQp6]yBj&amp;Mu2X$.Ps:d<br \/>\n# unscrambled: 1234<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 6<br \/>\n# password   : 12345<br \/>\n# scrambled  : cn9a*l5W(KqBc.YxGs4a1&gt;e0Ue<br \/>\n# unscrambled: 12345<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 7<br \/>\n# password   : 123456<br \/>\n# scrambled  : ctCm;!CsFn9i3Z0R}QsEt8h;KuDne<br \/>\n# unscrambled: 123456<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 8<br \/>\n# password   : 1234567<br \/>\n# scrambled  : d|O}P&gt;d:m5jAm&lt;p?lDl@s&gt;oGkCv0]1_f<br \/>\n# unscrambled: 1234567<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 9<br \/>\n# password   : 12345678<br \/>\n# scrambled  : d&amp;X0bP{S.V2h3kEtG}O#Z*]9c;r&gt;tOh&gt;oEf<br \/>\n# unscrambled: 12345678<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 10<br \/>\n# password   : 123456789<br \/>\n# scrambled  : !L5xbdG0wXD1lWE}mX@#nQ9%eM;vcO-ve#<br \/>\n# unscrambled: 123456789<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 11<br \/>\n# password   : 1234567890<br \/>\n# scrambled  : !R?)qvaH9{hV&lt;)uWH8tgU@&amp;tZF7weT8%sSE6#<br \/>\n# unscrambled: 1234567890<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 12<br \/>\n# password   : 1234567890a<br \/>\n# scrambled  : #q[B1&amp;oXL6!qWH:zm_B6&amp;oXL6}qWG:zl_B5&amp;%<br \/>\n# unscrambled: 1234567890a<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 13<br \/>\n# password   : 11111111111<br \/>\n# scrambled  : #oPD5{eUA)wbK&gt;$m_F3#gSF*uhK;-l[N1}p%<br \/>\n# unscrambled: 11111111111<br \/>\n# status     : OK<\/p>\n<p># ================================================<br \/>\n# test       : 14<br \/>\n# password   : 1234567890ab<br \/>\n# scrambled  : ${eWKH9!spbPI8*}kaVB91tnfUF?-zsaSL7-$jcY&amp;<br \/>\n# unscrambled: 1234567890ab<br \/>\n# status     : OK<\/p>\n<p># SUMMARY<br \/>\n#   Passed : 14<br \/>\n#   Failed : 0<br \/>\n#   Total  : 14<br \/>\n[\/crayon]<\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The NX web page entitled &#8220;The password scrambling algorithm in NX client&#8221; describes their password scrambling algorithm in C++ and perl but it does not describe it in python. Nor does it describe how to unscramble the data. This blog describes how I implemented both algorithms in python. I was surprised that NX didn&#8217;t use &hellip; <a href=\"https:\/\/joelinoff.com\/blog\/?p=156\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">NX password scrambling and unscrambling algorithms in python 2.7<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[5,7],"tags":[],"_links":{"self":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156"}],"collection":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=156"}],"version-history":[{"count":20,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156\/revisions"}],"predecessor-version":[{"id":398,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156\/revisions\/398"}],"wp:attachment":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}