Assuming we're dealing with a linear filter, we can use deconvolution to find the kernel. Usually, deconvolution operations estimate the original input image $f$ in the operation $f*h=g$, given the kernel $h$ and the blurred image $g$. But since convolution is commutative, we can simply change the places of $f$ and $h$. This means we can use standard deconvolution tools to compute the kernel form an input and output image pair:
import diplib as dip
input = dip.ImageRead('input.jpg')
blurry = dip.ImageRead('blurry.jpg')
# Use green channel to compute kernel
kernel_estimated = dip.WienerDeconvolution(blurry(1), input(1), regularization=1e-9, options={"pad"})
kernel_estimated.Show()
I'm using DIPlib here, because this is what I'm familiar with (disclosure: I'm an author), but you can use any library for this that does deconvolution. I used a very small amount of regularization because there's little noise other than JPEG compression artifacts. You can manually increase this value until noise doesn't dominate the output. If it's too large, the output will be too smooth, and the kernel will then to look like a Gaussian.
This is the result:
The cross is likely caused by JPEG compression artifacts. The gray background is just noise around the 0 value, there exist negative values in this image.
We also notice that the kernel is shifted. This means that the input and output are not aligned.
With negative value set to 0, and zooming in to the middle of the image, the kernel looks like this:
I'm not sure if it's just a ring or if the interior has some intensity.
Assuming it's just a ring (diameter is about 27, the profile looks like a Gaussian with sigma 2), we can recreate it like so:
kernel = dip.Image((35, 35), 1, "SFLOAT")
kernel.Fill(0)
dip.DrawBandlimitedBall(kernel, diameter=27, origin=(35//2, 35//2), mode="empty", sigma=2)
kernel /= dip.Sum(kernel)
out = dip.Convolution(input, kernel)
This does not look exactly like the blurry image, but has similar properties. I might have misjudged the ring diameter, we might need to fill up the ring with an intermediate gray value, maybe our ring is too smooth, and maybe there's a different kernel for the red and blue channels.