TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Is there a feasible preimage attack for any hash function today?

2 pointsby tgamblinabout 2 years ago

2 comments

dalkeabout 2 years ago
(I notified Martin and the FitNesse user mailing list about this back in 2010. I assume their threat model is that the default hash function is about the same as a closed office door - a request to stay out, or at least knock first - rather than a strong preventative measure.)<p>In this comment I&#x27;ll demonstrate that 1) there is a hash function, 2) in use, 3) with a successful preimage attack.<p>&quot;Uncle&quot; Bob Martin&#x27;s &quot;FitNesse&quot;, see <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;FitNesse" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;FitNesse</a> and <a href="http:&#x2F;&#x2F;fitnesse.org&#x2F;" rel="nofollow">http:&#x2F;&#x2F;fitnesse.org&#x2F;</a> , uses its own hash function, at <a href="https:&#x2F;&#x2F;github.com&#x2F;unclebob&#x2F;fitnesse&#x2F;blob&#x2F;master&#x2F;src&#x2F;fitnesse&#x2F;authentication&#x2F;HashingCipher.java">https:&#x2F;&#x2F;github.com&#x2F;unclebob&#x2F;fitnesse&#x2F;blob&#x2F;master&#x2F;src&#x2F;fitness...</a> .<p>The Python equivalent is:<p><pre><code> import base64 repetitions = 3 lock_bytes = b&quot;Like a long-leggedfly upon the stream\nHis mind moves upon silence.&quot; def encrypt(value): result = [0] * 15 for i, byte in enumerate(lock_bytes): result[i%15] += (byte + repetitions*ord(value[i%len(value)])) s = bytes([(i % 256) for i in result]) return base64.b64encode(s) </code></pre> Here&#x27;s an example:<p><pre><code> &gt;&gt;&gt; encrypt(&quot;Swordfish&quot;) b&#x27;YW5a6EcE5U6LOQbfTb+M&#x27; &gt;&gt;&gt; encrypt(&quot;Too many secrets&quot;) b&#x27;2fNpXgkyNCrUJArsdIat&#x27; </code></pre> This is a very weak hash function. With a bit of tweaking the above function is:<p><pre><code> init_result = [0] * 15 for i, byte in enumerate(lock_bytes): init_result[i%15] += byte init_result = [value % 256 for value in init_result] # init_result = [163, 198, 30, 15, 204, 206, 32, 11, 156, 182, 183, 219, 109, 169, 163] def encrypt(value): N = len(value) result = [None] * 15 for b in range(15): result[b] = (init_result[b] + (3 * sum(ord(value[i%N]) for i in range(b, 66, 15)))) % 256 return base64.b64encode(bytes(result)) </code></pre> where the lock_bytes is not used during the encoding.<p>To simplify preimage construction, the generated preimage will have 66 bytes, matching len(lock_bytes). We need to find values for positions 0, 15, 30, 45, and 60 such that their value, plus the initial value for hash[0], adds up to the expected value for hash[0], and so on. One such solution is:<p><pre><code> import itertools def get_preimage(expected): result = base64.b64decode(expected) assert len(result) == 15, result input_bytes = bytearray(b&quot;A&quot; * 66) # start with all &#x27;A&#x27;s for b, needed_value in enumerate(result): offsets = list(range(b, 66, 15)) needed_value = (needed_value - init_result[b] + 256) % 256 # Adjust the characters one-by-one until finding a match. # This works because 3 and 256 are relatively prime, finding # a solution within 256 update attempts. # (There&#x27;s probably a more elegant solution.) positions = range(b, 66, 15) for change_pos in itertools.cycle(positions): value = (3*sum(input_bytes[pos] for pos in positions)) % 256 if value == needed_value: break input_bytes[change_pos] += 1 return &quot;&quot;.join(map(chr, input_bytes)) </code></pre> To demonstrate:<p><pre><code> &gt;&gt;&gt; h = encrypt(&quot;Swordfish&quot;) &gt;&gt;&gt; h b&#x27;YW5a6EcE5U6LOQbfTb+M&#x27; &gt;&gt;&gt; s = get_preimage(h) &gt;&gt;&gt; s &#x27;brkdojfqjarkhmibrkdojfpi`qkhmibrjdojfpi`qkhlibqjdnjepi`qkhlhbqjcnj&#x27; &gt;&gt;&gt; encrypt(s) b&#x27;YW5a6EcE5U6LOQbfTb+M&#x27; &gt;&gt;&gt; encrypt(s) == h True </code></pre> For example, the documentation at <a href="http:&#x2F;&#x2F;fitnesse.org&#x2F;FitNesse.UserGuide.AdministeringFitNesse.PasswordFile" rel="nofollow">http:&#x2F;&#x2F;fitnesse.org&#x2F;FitNesse.UserGuide.AdministeringFitNesse...</a> shows how to generate password hashes. I&#x27;ll follow those steps:<p><pre><code> % env CLASSPATH=fitnesse-standalone.jar java fitnesse.authentication.Password Leonardo Be advised, the password will be visible as it is typed. enter password for Leonardo: katana confirm password: katana password saved in passwords.txt % cat passwords.txt !fitnesse.authentication.HashingCipher Leonardo:rMN4+vDv6OWafpHZNYOh </code></pre> (The documentation says &quot;katana&quot; hashes to &quot;VEN4CfBvGCSafZDZNIKh&quot;, which is incorrect.)<p>I&#x27;ll now use the above Python code to verify that it matches the FitNesse hash, and that it generates a successful preimage:<p><pre><code> &gt;&gt;&gt; encrypt(&quot;katana&quot;) b&#x27;rMN4+vDv6OWafpHZNYOh&#x27; &gt;&gt;&gt; get_preimage(b&quot;rMN4+vDv6OWafpHZNYOh&quot;) &#x27;ggmeiifhkfhkfhkgfmeiifhkfhkfhkgfleiifgjfgjfgjgfleihfgjfgjfgjgflehh&#x27; &gt;&gt;&gt; encrypt(&quot;ggmeiifhkfhkfhkgfmeiifhkfhkfhkgfleiifgjfgjfgjgfleihfgjfgjfgjgflehh&quot;) b&#x27;rMN4+vDv6OWafpHZNYOh&#x27; </code></pre> Remember, leave cryptographic hashing to the experts. ;)<p>It was even worse back in 2010 because the FitNesse web server allowed a path traversal attack, making it possible to request &quot;..&#x2F;passwords.txt&quot; and get the default password file for the server. This has since been fixed.
yunohnabout 2 years ago
Question:<p>&gt; Is there a feasible preimage attack for any hash function?<p>Answer:<p>&gt; Yes, certainly<p>&gt; memory requirements make it impractical<p>I’d like to think feasible != impractical…