Find target hash collisions for Apple's NeuralHash perceptual hash function.

For example, starting from a picture of this
we can find an adversarial image that has the same hash as the
of the dog in this post:

python --image cat.jpg --target 59a34eabe31910abfb06f308

Cat image with NeuralHash 59a34eabe31910abfb06f308 Dog image with NeuralHash 59a34eabe31910abfb06f308

We can confirm the hash collision using from

$ python dog.png
$ python adv.png

How it works

NeuralHash is a perceptual hash
that uses a neural
network. Images are resized to 360x360 and passed through a neural network to
produce a 128-dimensional feature vector. Then, the vector is projected onto
R^96 using a 128x96 "seed" matrix. Finally, to produce a 96-bit hash, the
96-dimensional vector is thresholded: negative entries turn into a 0 bit, and
non-negative entries turn into a 1 bit.

This entire process, except for the thresholding, is differentiable, so we can
use gradient descent to find hash collisions. This is a well-known property of
neural networks, that they are vulnerable to adversarial

We can define a loss that captures how close an image is to a given target
hash: this loss is basically just the NeuralHash algorithm as described above,
but with the final "hard" thresholding step tweaked so that it is "soft" (in
particular, differentiable). Exactly how this is done (choices of activation
functions, parameters, etc.) can affect convergence, so it can require some
experimentation. After choosing the loss function, we can follow the standard
method to find adversarial examples for neural networks: gradient descent.


The implementation currently does an alternating projections style attack to
find an adversarial example that has the intended hash and also looks similar
to the original. See for the full details. The implementation uses
two different loss functions: one measures the distance to the target hash, and
the other measures the quality of the perturbation (l2 norm + total variation).
We first optimize for a collision, focusing only on matching the target hash.
Once we find a projection, we alternate between minimizing the perturbation and
ensuring that the hash value does not change. The attack has a number of
parameters; run python --help or refer to the code for a full
list. Tweaking these parameters can make a big difference in convergence time
and the quality of the output.

The implementation also supports a flag --blur [sigma] that blurs the
perturbation on every step of the search. This can slow down or break
convergence, but on some examples, it can be helpful for getting results that
look more natural and less like glitch art.


Reproducing the Lena/Barbara result from this post:

The first image above is the original Lena image. The second was produced with --target a426dae78cc63799d01adc32 to collide with Barbara. The third was produced with the additional argument --blur 1.0. The fourth is the original Barbara image. Checking their hashes:

$ python lena.png
$ python lena-adv.png
$ python lena-adv-blur-1.0.png
$ python barbara.png

Reproducing the Picard/Sidious result from this post:

The first image above is the original Picard image. The second was produced with --target e34b3da852103c3c0828fbd1 --tv-weight 3e-4 to collide with Sidious. The third was produced with the additional argument --blur 0.5. The fourth is the original Sidious image. Checking their hashes:

$ python picard.png
$ python picard-adv.png
$ python picard-adv-blur-0.5.png
$ python sidious.png


  • Get Apple's NeuralHash model following the instructions in
    AsuharietYgvar/AppleNeuralHash2ONNX and either put all the
    files in this directory or supply the --model / --seed arguments
  • Install Python dependencies: pip install -r requirements.txt


Run python --image [path to image] --target [target hash] to
generate a hash collision. Run python --help to see all the
options, including some knobs you can tweak, like the learning rate and some
other parameters.


The code in this repository is intended to be a demonstration, and perhaps a
starting point for other exploration. Tweaking the implementation (choice of
loss function, choice of parameters, etc.) might produce much better results
than this code currently achieves.